@moltium/world-cli 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.
Files changed (3) hide show
  1. package/README.md +205 -0
  2. package/dist/index.js +805 -0
  3. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # @moltium/world-cli
2
+
3
+ CLI tool for creating and managing Moltium World SDK projects on the Monad blockchain.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @moltium/world-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Create a new world project
15
+ moltium-world init my-world
16
+
17
+ # Navigate to project
18
+ cd my-world
19
+
20
+ # Install dependencies
21
+ npm install
22
+
23
+ # Deploy smart contracts
24
+ npm run deploy:contracts
25
+
26
+ # Start your world
27
+ npm run dev
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ ### `moltium-world init [name]`
33
+
34
+ Initialize a new world project with interactive prompts.
35
+
36
+ **Options:**
37
+ - `-d, --directory <path>` - Output directory
38
+
39
+ **Example:**
40
+ ```bash
41
+ moltium-world init my-awesome-world
42
+ ```
43
+
44
+ ### `moltium-world token deploy`
45
+
46
+ Deploy a custom ERC20 token for your world.
47
+
48
+ **Options:**
49
+ - `-c, --config <path>` - Token config file (default: `./token.config.json`)
50
+ - `-r, --rpc-url <url>` - RPC URL (overrides .env)
51
+ - `-s, --silent` - No interactive prompts
52
+
53
+ **Example:**
54
+ ```bash
55
+ moltium-world token deploy
56
+ ```
57
+
58
+ ### `moltium-world token verify`
59
+
60
+ Verify your token contract on Sourcify.
61
+
62
+ **Options:**
63
+ - `-a, --address <address>` - Token contract address
64
+
65
+ **Example:**
66
+ ```bash
67
+ moltium-world token verify --address 0x...
68
+ ```
69
+
70
+ ### `moltium-world deploy`
71
+
72
+ Deploy AgentRegistry and WorldMembership contracts.
73
+
74
+ **Options:**
75
+ - `-r, --rpc-url <url>` - RPC URL (overrides .env)
76
+
77
+ **Example:**
78
+ ```bash
79
+ moltium-world deploy
80
+ ```
81
+
82
+ ### `moltium-world start`
83
+
84
+ Start the world server.
85
+
86
+ **Options:**
87
+ - `-p, --port <port>` - Server port (default: 4000)
88
+ - `--dev` - Development mode with auto-reload
89
+
90
+ **Example:**
91
+ ```bash
92
+ moltium-world start --dev
93
+ ```
94
+
95
+ ## Token Options
96
+
97
+ During project initialization, you can choose between two token types:
98
+
99
+ ### Default Token (MON)
100
+
101
+ Use Monad's native currency for all world transactions.
102
+
103
+ - ✅ No deployment needed
104
+ - ✅ Instant liquidity
105
+ - ✅ Lower gas costs
106
+
107
+ ### Custom ERC20
108
+
109
+ Deploy your own world token with custom tokenomics.
110
+
111
+ - ✅ Branded currency
112
+ - ✅ Custom economics
113
+ - ✅ Full control over supply
114
+
115
+ ## Project Structure
116
+
117
+ After running `init`, your project will have:
118
+
119
+ ```
120
+ my-world/
121
+ ├── src/
122
+ │ └── index.ts # World entry point
123
+ ├── world.config.json # World configuration
124
+ ├── token.config.json # Token config (if custom)
125
+ ├── .env # Environment variables
126
+ ├── package.json
127
+ └── README.md
128
+ ```
129
+
130
+ ## Configuration
131
+
132
+ ### world.config.json
133
+
134
+ Main configuration file for your world:
135
+
136
+ ```json
137
+ {
138
+ "name": "MyWorld",
139
+ "description": "An AI agent world",
140
+ "type": "generic",
141
+ "server": {
142
+ "port": 4000,
143
+ "host": "localhost"
144
+ },
145
+ "admission": {
146
+ "maxAgents": 100,
147
+ "entryFee": 0.1
148
+ },
149
+ "persistence": {
150
+ "type": "sqlite"
151
+ },
152
+ "blockchain": {
153
+ "rpcUrl": "https://rpc-testnet.monadinfra.com",
154
+ "chainId": 10143
155
+ }
156
+ }
157
+ ```
158
+
159
+ ### token.config.json
160
+
161
+ Token configuration (for custom tokens):
162
+
163
+ ```json
164
+ {
165
+ "type": "custom",
166
+ "name": "MyWorld Token",
167
+ "symbol": "MWT",
168
+ "decimals": 18,
169
+ "initialSupply": "1000000",
170
+ "maxSupply": "10000000",
171
+ "mintable": true,
172
+ "burnable": true
173
+ }
174
+ ```
175
+
176
+ ### .env
177
+
178
+ Environment variables:
179
+
180
+ ```bash
181
+ MONAD_RPC_URL=https://rpc-testnet.monadinfra.com
182
+ DEPLOYER_PRIVATE_KEY=0x...
183
+ AGENT_REGISTRY_ADDRESS=
184
+ WORLD_MEMBERSHIP_ADDRESS=
185
+ ```
186
+
187
+ ## Development
188
+
189
+ ```bash
190
+ # Install dependencies
191
+ npm install
192
+
193
+ # Build CLI
194
+ npm run build
195
+
196
+ # Run in development mode
197
+ npm run dev
198
+
199
+ # Test
200
+ npm test
201
+ ```
202
+
203
+ ## License
204
+
205
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,805 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/index.ts
5
+ import { Command as Command5 } from "commander";
6
+ import chalk5 from "chalk";
7
+
8
+ // src/commands/init.ts
9
+ import { Command } from "commander";
10
+ import inquirer from "inquirer";
11
+ import chalk from "chalk";
12
+ import ora from "ora";
13
+ import fs from "fs/promises";
14
+ import path from "path";
15
+
16
+ // src/lib/config-generator.ts
17
+ function generateWorldConfig(options) {
18
+ return {
19
+ name: options.name,
20
+ description: options.description,
21
+ type: options.worldType,
22
+ server: {
23
+ port: 4e3,
24
+ host: "localhost"
25
+ },
26
+ admission: {
27
+ maxAgents: 100,
28
+ entryFee: parseFloat(options.entryFee),
29
+ requiredSkills: [],
30
+ requiredTags: []
31
+ },
32
+ simulation: {
33
+ tickIntervalMs: 5e3,
34
+ autoStart: false
35
+ },
36
+ persistence: {
37
+ type: options.persistenceType,
38
+ ...options.persistenceType === "sqlite" && {
39
+ sqlite: {
40
+ filename: "./data/world.db"
41
+ }
42
+ }
43
+ },
44
+ blockchain: {
45
+ rpcUrl: process.env.MONAD_RPC_URL || "https://rpc-testnet.monadinfra.com",
46
+ chainId: 10143,
47
+ requireMembership: true,
48
+ enforceOnChainValidation: false,
49
+ ...options.tokenType === "custom" && {
50
+ useCustomToken: true
51
+ }
52
+ },
53
+ logging: {
54
+ level: "info",
55
+ format: "json"
56
+ }
57
+ };
58
+ }
59
+ function generateTokenConfig(tokenConfig) {
60
+ return {
61
+ type: "custom",
62
+ name: tokenConfig.tokenName,
63
+ symbol: tokenConfig.tokenSymbol,
64
+ decimals: 18,
65
+ initialSupply: tokenConfig.initialSupply,
66
+ maxSupply: tokenConfig.maxSupply || null,
67
+ mintable: tokenConfig.mintable !== false,
68
+ burnable: tokenConfig.burnable !== false,
69
+ metadata: {
70
+ description: `Official token for ${tokenConfig.tokenName}`
71
+ }
72
+ };
73
+ }
74
+ function generateEnvFile(options) {
75
+ return `# Monad Network Configuration
76
+ MONAD_RPC_URL=https://rpc-testnet.monadinfra.com
77
+
78
+ # Deployer Wallet
79
+ # IMPORTANT: Keep this secret! Never commit to version control
80
+ DEPLOYER_PRIVATE_KEY=
81
+
82
+ # Deployed Contract Addresses (populated after deployment)
83
+ AGENT_REGISTRY_ADDRESS=
84
+ WORLD_MEMBERSHIP_ADDRESS=
85
+ ${options.tokenType === "custom" ? "WORLD_TOKEN_ADDRESS=\n" : ""}
86
+ # Optional: Monad Block Explorer API Key
87
+ # MONAD_ETHERSCAN_KEY=
88
+ `;
89
+ }
90
+
91
+ // src/templates/world-template.ts
92
+ function generateWorldTemplate(config) {
93
+ const files = {};
94
+ files["src/index.ts"] = `import { World } from '@moltium/world-core';
95
+ import { loadWorldConfig } from '@moltium/world-core/config';
96
+ import { startWorldServer } from '@moltium/world-core/server';
97
+ import dotenv from 'dotenv';
98
+
99
+ dotenv.config();
100
+
101
+ async function main() {
102
+ // Load configuration
103
+ const config = await loadWorldConfig('./world.config.json');
104
+
105
+ // Create world instance
106
+ const world = new World(config);
107
+
108
+ // Initialize world (restore state, discover agents)
109
+ await world.init();
110
+
111
+ // Start HTTP server
112
+ await startWorldServer(world);
113
+
114
+ console.log(\`\u{1F30D} ${config.name} is running!\`);
115
+ console.log(\` Server: http://\${config.server.host}:\${config.server.port}\`);
116
+ console.log(\` Agents: \${world.getAgents().length}/\${config.admission.maxAgents}\`);
117
+
118
+ // Graceful shutdown
119
+ process.on('SIGINT', async () => {
120
+ console.log('\\n\\n\u{1F6D1} Shutting down...');
121
+ await world.stop();
122
+ process.exit(0);
123
+ });
124
+ }
125
+
126
+ main().catch((error) => {
127
+ console.error('Failed to start world:', error);
128
+ process.exit(1);
129
+ });
130
+ `;
131
+ if (config.worldType === "game") {
132
+ files["src/game-logic.ts"] = generateGameLogic();
133
+ } else if (config.worldType === "marketplace") {
134
+ files["src/marketplace-logic.ts"] = generateMarketplaceLogic();
135
+ }
136
+ return files;
137
+ }
138
+ function generateGameLogic() {
139
+ return `/**
140
+ * Game-specific world logic
141
+ */
142
+
143
+ export interface GameState {
144
+ round: number;
145
+ turn: number;
146
+ currentPlayer?: string;
147
+ scores: Record<string, number>;
148
+ }
149
+
150
+ export function initGameState(): GameState {
151
+ return {
152
+ round: 0,
153
+ turn: 0,
154
+ scores: {},
155
+ };
156
+ }
157
+
158
+ export function processTurn(state: GameState, agentUrl: string, action: any): GameState {
159
+ // Implement your game logic here
160
+ return {
161
+ ...state,
162
+ turn: state.turn + 1,
163
+ scores: {
164
+ ...state.scores,
165
+ [agentUrl]: (state.scores[agentUrl] || 0) + 1,
166
+ },
167
+ };
168
+ }
169
+ `;
170
+ }
171
+ function generateMarketplaceLogic() {
172
+ return `/**
173
+ * Marketplace-specific world logic
174
+ */
175
+
176
+ export interface MarketplaceListing {
177
+ id: string;
178
+ seller: string;
179
+ item: string;
180
+ price: number;
181
+ timestamp: number;
182
+ }
183
+
184
+ export interface MarketplaceState {
185
+ listings: MarketplaceListing[];
186
+ transactions: any[];
187
+ }
188
+
189
+ export function initMarketplaceState(): MarketplaceState
190
+
191
+ {
192
+ return {
193
+ listings: [],
194
+ transactions: [],
195
+ };
196
+ }
197
+
198
+ export function createListing(
199
+ state: MarketplaceState,
200
+ seller: string,
201
+ item: string,
202
+ price: number
203
+ ): MarketplaceState {
204
+ const listing: MarketplaceListing = {
205
+ id: crypto.randomUUID(),
206
+ seller,
207
+ item,
208
+ price,
209
+ timestamp: Date.now(),
210
+ };
211
+
212
+ return {
213
+ ...state,
214
+ listings: [...state.listings, listing],
215
+ };
216
+ }
217
+ `;
218
+ }
219
+
220
+ // src/commands/init.ts
221
+ var initCommand = new Command("init").description("Initialize a new world project").argument("[name]", "World name").option("-d, --directory <path>", "Output directory").action(async (name, options) => {
222
+ console.log(chalk.bold.cyan("\n\u{1F30D} Moltium World SDK - Project Initialization\n"));
223
+ const answers = await inquirer.prompt([
224
+ {
225
+ type: "input",
226
+ name: "name",
227
+ message: "World name:",
228
+ default: name || "my-world",
229
+ validate: (input) => input.length > 0 || "Name is required"
230
+ },
231
+ {
232
+ type: "input",
233
+ name: "description",
234
+ message: "Description:",
235
+ default: "An AI agent world"
236
+ },
237
+ {
238
+ type: "list",
239
+ name: "worldType",
240
+ message: "World type:",
241
+ choices: [
242
+ { name: "Generic - Open-ended multi-agent environment", value: "generic" },
243
+ { name: "Game - Turn-based game world", value: "game" },
244
+ { name: "Marketplace - Agent trading and commerce", value: "marketplace" },
245
+ { name: "Simulation - Custom simulation environment", value: "simulation" }
246
+ ],
247
+ default: "generic"
248
+ },
249
+ {
250
+ type: "list",
251
+ name: "tokenType",
252
+ message: "Token type:",
253
+ choices: [
254
+ {
255
+ name: "Default (MON) - Use Monad's native currency",
256
+ value: "native",
257
+ short: "MON"
258
+ },
259
+ {
260
+ name: "Custom ERC20 - Deploy your own world token",
261
+ value: "custom",
262
+ short: "Custom"
263
+ }
264
+ ],
265
+ default: "native"
266
+ }
267
+ ]);
268
+ let tokenConfig = null;
269
+ if (answers.tokenType === "custom") {
270
+ const tokenAnswers = await inquirer.prompt([
271
+ {
272
+ type: "input",
273
+ name: "tokenName",
274
+ message: "Token name:",
275
+ default: `${answers.name} Token`
276
+ },
277
+ {
278
+ type: "input",
279
+ name: "tokenSymbol",
280
+ message: "Token symbol (3-5 characters):",
281
+ default: answers.name.substring(0, 3).toUpperCase(),
282
+ validate: (input) => input.length >= 3 && input.length <= 5 || "Symbol must be 3-5 characters"
283
+ },
284
+ {
285
+ type: "input",
286
+ name: "initialSupply",
287
+ message: "Initial supply:",
288
+ default: "1000000",
289
+ validate: (input) => !isNaN(Number(input)) || "Must be a number"
290
+ },
291
+ {
292
+ type: "confirm",
293
+ name: "advancedTokenomics",
294
+ message: "Configure advanced tokenomics?",
295
+ default: false
296
+ }
297
+ ]);
298
+ let advancedConfig = {};
299
+ if (tokenAnswers.advancedTokenomics) {
300
+ const advanced = await inquirer.prompt([
301
+ {
302
+ type: "input",
303
+ name: "maxSupply",
304
+ message: "Maximum supply (leave empty for unlimited):",
305
+ default: ""
306
+ },
307
+ {
308
+ type: "confirm",
309
+ name: "mintable",
310
+ message: "Allow minting new tokens?",
311
+ default: true
312
+ },
313
+ {
314
+ type: "confirm",
315
+ name: "burnable",
316
+ message: "Allow burning tokens?",
317
+ default: true
318
+ }
319
+ ]);
320
+ advancedConfig = advanced;
321
+ }
322
+ tokenConfig = { ...tokenAnswers, ...advancedConfig };
323
+ }
324
+ const persistenceAnswers = await inquirer.prompt([
325
+ {
326
+ type: "list",
327
+ name: "persistenceType",
328
+ message: "Persistence backend:",
329
+ choices: [
330
+ { name: "SQLite - Zero-config file database (recommended)", value: "sqlite" },
331
+ { name: "Memory - In-memory only (development)", value: "memory" },
332
+ { name: "PostgreSQL - Production-grade SQL", value: "postgres" },
333
+ { name: "Redis - High-speed in-memory", value: "redis" },
334
+ { name: "MongoDB - Document database", value: "mongo" },
335
+ { name: "LevelDB - Embedded key-value", value: "leveldb" }
336
+ ],
337
+ default: "sqlite"
338
+ }
339
+ ]);
340
+ const entryFeeAnswers = await inquirer.prompt([
341
+ {
342
+ type: "input",
343
+ name: "entryFee",
344
+ message: answers.tokenType === "native" ? "Entry fee (in MON):" : "Entry fee (in tokens):",
345
+ default: "0.1",
346
+ validate: (input) => !isNaN(Number(input)) || "Must be a number"
347
+ }
348
+ ]);
349
+ const spinner = ora("Creating world project...").start();
350
+ try {
351
+ const projectDir = options.directory || path.join(process.cwd(), answers.name);
352
+ await fs.mkdir(projectDir, { recursive: true });
353
+ await fs.mkdir(path.join(projectDir, "src"), { recursive: true });
354
+ await fs.mkdir(path.join(projectDir, "scripts"), { recursive: true });
355
+ const worldConfig = generateWorldConfig({
356
+ ...answers,
357
+ ...persistenceAnswers,
358
+ ...entryFeeAnswers
359
+ });
360
+ await fs.writeFile(
361
+ path.join(projectDir, "world.config.json"),
362
+ JSON.stringify(worldConfig, null, 2)
363
+ );
364
+ if (tokenConfig) {
365
+ const tokenConfigContent = generateTokenConfig(tokenConfig);
366
+ await fs.writeFile(
367
+ path.join(projectDir, "token.config.json"),
368
+ JSON.stringify(tokenConfigContent, null, 2)
369
+ );
370
+ }
371
+ const envContent = generateEnvFile({ tokenType: answers.tokenType });
372
+ await fs.writeFile(path.join(projectDir, ".env"), envContent);
373
+ await fs.writeFile(
374
+ path.join(projectDir, ".gitignore"),
375
+ "node_modules\n.env\n.env.local\n*.log\ndata/\n*.db\n.DS_Store\n"
376
+ );
377
+ const templateFiles = generateWorldTemplate({
378
+ name: answers.name,
379
+ worldType: answers.worldType,
380
+ tokenType: answers.tokenType
381
+ });
382
+ for (const [filename, content] of Object.entries(templateFiles)) {
383
+ await fs.writeFile(path.join(projectDir, filename), content);
384
+ }
385
+ const packageJson = {
386
+ name: answers.name,
387
+ version: "0.1.0",
388
+ description: answers.description,
389
+ type: "module",
390
+ scripts: {
391
+ dev: "tsx src/index.ts",
392
+ start: "node dist/index.js",
393
+ build: "tsup",
394
+ "token:deploy": "moltium-world token deploy",
395
+ "token:verify": "moltium-world token verify",
396
+ "deploy:contracts": "moltium-world deploy"
397
+ },
398
+ dependencies: {
399
+ "@moltium/world-core": "^0.1.0",
400
+ dotenv: "^16.4.0"
401
+ },
402
+ devDependencies: {
403
+ "@types/node": "^22.0.0",
404
+ tsup: "^8.3.0",
405
+ tsx: "^4.7.0",
406
+ typescript: "^5.3.0"
407
+ }
408
+ };
409
+ await fs.writeFile(
410
+ path.join(projectDir, "package.json"),
411
+ JSON.stringify(packageJson, null, 2)
412
+ );
413
+ const tsConfig = {
414
+ compilerOptions: {
415
+ target: "ES2022",
416
+ module: "ESNext",
417
+ moduleResolution: "bundler",
418
+ esModuleInterop: true,
419
+ strict: true,
420
+ skipLibCheck: true,
421
+ outDir: "./dist",
422
+ rootDir: "./src"
423
+ },
424
+ include: ["src/**/*"],
425
+ exclude: ["node_modules", "dist"]
426
+ };
427
+ await fs.writeFile(
428
+ path.join(projectDir, "tsconfig.json"),
429
+ JSON.stringify(tsConfig, null, 2)
430
+ );
431
+ const readme = generateReadme({
432
+ name: answers.name,
433
+ description: answers.description,
434
+ tokenType: answers.tokenType,
435
+ hasCustomToken: tokenConfig !== null
436
+ });
437
+ await fs.writeFile(path.join(projectDir, "README.md"), readme);
438
+ spinner.succeed(chalk.green("World project created successfully!"));
439
+ console.log(chalk.bold("\n\u2728 Next steps:\n"));
440
+ console.log(chalk.gray(` cd ${answers.name}`));
441
+ console.log(chalk.gray(" npm install"));
442
+ if (tokenConfig) {
443
+ console.log(chalk.gray(" npm run token:deploy # Deploy your custom token"));
444
+ }
445
+ console.log(chalk.gray(" npm run deploy:contracts # Deploy AgentRegistry & WorldMembership"));
446
+ console.log(chalk.gray(" npm run dev # Start your world\n"));
447
+ if (tokenConfig) {
448
+ console.log(chalk.yellow("\u26A0\uFE0F Remember to configure DEPLOYER_PRIVATE_KEY in .env before deploying contracts\n"));
449
+ }
450
+ } catch (error) {
451
+ spinner.fail(chalk.red("Failed to create project"));
452
+ console.error(chalk.red(error.message));
453
+ process.exit(1);
454
+ }
455
+ });
456
+ function generateReadme(config) {
457
+ return `# ${config.name}
458
+
459
+ ${config.description}
460
+
461
+ ## Getting Started
462
+
463
+ \`\`\`bash
464
+ # Install dependencies
465
+ npm install
466
+
467
+ ${config.hasCustomToken ? "# Deploy custom token\nnpm run token:deploy\n" : ""}# Deploy smart contracts
468
+ npm run deploy:contracts
469
+
470
+ # Start world server
471
+ npm run dev
472
+ \`\`\`
473
+
474
+ ## Configuration
475
+
476
+ - **World config:** \`world.config.json\`
477
+ ${config.hasCustomToken ? "- **Token config:** `token.config.json`\n" : ""}- **Environment:** \`.env\`
478
+
479
+ ## Token Type
480
+
481
+ ${config.tokenType === "native" ? "**Native (MON)** - This world uses Monad's native currency for transactions." : "**Custom ERC20** - This world has its own custom token. Deploy it using `npm run token:deploy`."}
482
+
483
+ ## Commands
484
+
485
+ - \`npm run dev\` - Start world in development mode
486
+ - \`npm run build\` - Build for production
487
+ - \`npm run start\` - Start production server
488
+ - \`npm run deploy:contracts\` - Deploy smart contracts to Monad
489
+ ${config.hasCustomToken ? "- `npm run token:deploy` - Deploy custom token\n- `npm run token:verify` - Verify token contract\n" : ""}
490
+ ## Documentation
491
+
492
+ For more information, visit: https://moltium.world/docs
493
+ `;
494
+ }
495
+
496
+ // src/commands/token.ts
497
+ import { Command as Command2 } from "commander";
498
+ import chalk2 from "chalk";
499
+ import ora2 from "ora";
500
+ import fs3 from "fs/promises";
501
+ import path3 from "path";
502
+ import { execSync } from "child_process";
503
+
504
+ // src/lib/config-loader.ts
505
+ import fs2 from "fs/promises";
506
+ import path2 from "path";
507
+ async function loadTokenConfig(configPath = "./token.config.json") {
508
+ try {
509
+ const fullPath = path2.resolve(configPath);
510
+ const content = await fs2.readFile(fullPath, "utf-8");
511
+ return JSON.parse(content);
512
+ } catch (error) {
513
+ throw new Error(`Failed to load token config: ${error.message}`);
514
+ }
515
+ }
516
+ async function loadWorldConfig(configPath = "./world.config.json") {
517
+ try {
518
+ const fullPath = path2.resolve(configPath);
519
+ const content = await fs2.readFile(fullPath, "utf-8");
520
+ return JSON.parse(content);
521
+ } catch (error) {
522
+ throw new Error(`Failed to load world config: ${error.message}`);
523
+ }
524
+ }
525
+
526
+ // src/lib/blockchain.ts
527
+ import { ethers } from "ethers";
528
+ async function checkWalletBalance(privateKey, rpcUrl) {
529
+ try {
530
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
531
+ const wallet = new ethers.Wallet(privateKey, provider);
532
+ const balance = await provider.getBalance(wallet.address);
533
+ return parseFloat(ethers.formatEther(balance));
534
+ } catch (error) {
535
+ throw new Error(`Failed to check wallet balance: ${error.message}`);
536
+ }
537
+ }
538
+ async function estimateGas(contractType) {
539
+ const estimates = {
540
+ token: 3e-3,
541
+ // ~3M gas
542
+ world: 0.31
543
+ // ~3M gas for both contracts
544
+ };
545
+ return estimates[contractType];
546
+ }
547
+
548
+ // src/commands/token.ts
549
+ var tokenCommand = new Command2("token").description("Manage world token deployment and operations");
550
+ tokenCommand.command("deploy").description("Deploy custom ERC20 token").option("-c, --config <path>", "Token config file", "./token.config.json").option("-r, --rpc-url <url>", "RPC URL (overrides .env)").option("-s, --silent", "No interactive prompts").action(async (options) => {
551
+ console.log(chalk2.bold.cyan("\n\u{1FA99} Deploying Custom Token\n"));
552
+ try {
553
+ const spinner = ora2("Loading configuration...").start();
554
+ const tokenConfig = await loadTokenConfig(options.config);
555
+ spinner.text = "Checking environment...";
556
+ const privateKey = process.env.DEPLOYER_PRIVATE_KEY;
557
+ if (!privateKey) {
558
+ spinner.fail();
559
+ console.error(chalk2.red("\u274C DEPLOYER_PRIVATE_KEY not found in .env file"));
560
+ console.log(chalk2.yellow("\\nPlease add your private key to .env:"));
561
+ console.log(chalk2.gray("DEPLOYER_PRIVATE_KEY=0x...\\n"));
562
+ process.exit(1);
563
+ }
564
+ const rpcUrl = options.rpcUrl || process.env.MONAD_RPC_URL || "https://rpc-testnet.monadinfra.com";
565
+ spinner.text = "Checking wallet balance...";
566
+ const balance = await checkWalletBalance(privateKey, rpcUrl);
567
+ spinner.text = "Estimating gas costs...";
568
+ const gasEstimate = await estimateGas("token");
569
+ spinner.succeed();
570
+ console.log(chalk2.bold("\\n\u{1F4CB} Deployment Details\\n"));
571
+ console.log(` ${chalk2.gray("Token Name:")} ${tokenConfig.name}`);
572
+ console.log(` ${chalk2.gray("Symbol:")} ${tokenConfig.symbol}`);
573
+ console.log(` ${chalk2.gray("Initial Supply:")} ${tokenConfig.initialSupply.toLocaleString()}`);
574
+ if (tokenConfig.maxSupply) {
575
+ console.log(` ${chalk2.gray("Max Supply:")} ${tokenConfig.maxSupply.toLocaleString()}`);
576
+ }
577
+ console.log(`\\n ${chalk2.gray("RPC URL:")} ${rpcUrl}`);
578
+ console.log(` ${chalk2.gray("Wallet Balance:")} ${balance} MON`);
579
+ console.log(` ${chalk2.gray("Estimated Gas:")} ~${gasEstimate} MON\\n`);
580
+ if (balance < gasEstimate) {
581
+ console.error(chalk2.red("\u274C Insufficient balance for deployment"));
582
+ console.log(chalk2.yellow(`\\n Need at least ${gasEstimate} MON, but have ${balance} MON\\n`));
583
+ process.exit(1);
584
+ }
585
+ const deploySpinner = ora2("Deploying token contract...").start();
586
+ try {
587
+ const env = {
588
+ ...process.env,
589
+ MONAD_RPC_URL: rpcUrl,
590
+ DEPLOYER_PRIVATE_KEY: privateKey,
591
+ TOKEN_NAME: tokenConfig.name,
592
+ TOKEN_SYMBOL: tokenConfig.symbol,
593
+ TOKEN_INITIAL_SUPPLY: tokenConfig.initialSupply.toString(),
594
+ TOKEN_MAX_SUPPLY: tokenConfig.maxSupply?.toString() || "0",
595
+ TOKEN_DECIMALS: tokenConfig.decimals?.toString() || "18"
596
+ };
597
+ const worldCorePath = path3.join(process.cwd(), "../world-core");
598
+ const command = `forge script script/DeployToken.s.sol:DeployTokenScript --rpc-url monad --broadcast -vvv`;
599
+ const output = execSync(command, {
600
+ cwd: worldCorePath,
601
+ env,
602
+ encoding: "utf-8"
603
+ });
604
+ const addressMatch = output.match(/Token deployed: (0x[a-fA-F0-9]{40})/);
605
+ const txMatch = output.match(/Transaction: (0x[a-fA-F0-9]{64})/);
606
+ if (!addressMatch) {
607
+ throw new Error("Could not parse deployed contract address");
608
+ }
609
+ const tokenAddress = addressMatch[1];
610
+ const txHash = txMatch?.[1];
611
+ deploySpinner.succeed(chalk2.green("Token deployed successfully!"));
612
+ console.log(chalk2.bold("\\n\u2705 Deployment Complete\\n"));
613
+ console.log(` ${chalk2.gray("Contract:")} ${chalk2.green(tokenAddress)}`);
614
+ if (txHash) {
615
+ console.log(` ${chalk2.gray("Transaction:")} ${txHash}`);
616
+ }
617
+ const updateSpinner = ora2("Updating world.config.json...").start();
618
+ try {
619
+ const worldConfigPath = path3.join(process.cwd(), "world.config.json");
620
+ const worldConfig = await loadWorldConfig(worldConfigPath);
621
+ worldConfig.blockchain = worldConfig.blockchain || {};
622
+ worldConfig.blockchain.worldTokenAddress = tokenAddress;
623
+ await fs3.writeFile(
624
+ worldConfigPath,
625
+ JSON.stringify(worldConfig, null, 2)
626
+ );
627
+ updateSpinner.succeed(chalk2.green("Configuration updated"));
628
+ } catch (error) {
629
+ updateSpinner.warn(chalk2.yellow("Could not update world.config.json"));
630
+ }
631
+ console.log(chalk2.bold("\\n\u{1F4DD} Next Steps:\\n"));
632
+ console.log(chalk2.gray(" 1. Verify contract: npm run token:verify"));
633
+ console.log(chalk2.gray(" 2. Start world: npm run dev\\n"));
634
+ } catch (error) {
635
+ deploySpinner.fail(chalk2.red("Deployment failed"));
636
+ console.error(chalk2.red("\\nError:"), error.message);
637
+ if (error.message.includes("insufficient funds")) {
638
+ console.log(chalk2.yellow("\\n\u2139\uFE0F You need more MON for gas fees\\n"));
639
+ }
640
+ process.exit(1);
641
+ }
642
+ } catch (error) {
643
+ console.error(chalk2.red("Error:"), error.message);
644
+ process.exit(1);
645
+ }
646
+ });
647
+ tokenCommand.command("verify").description("Verify token contract on Sourcify").option("-a, --address <address>", "Token contract address").action(async (options) => {
648
+ console.log(chalk2.bold.cyan("\\n\u{1F50D} Verifying Token Contract\\n"));
649
+ try {
650
+ let tokenAddress = options.address;
651
+ if (!tokenAddress) {
652
+ const spinner2 = ora2("Loading configuration...").start();
653
+ const worldConfig = await loadWorldConfig();
654
+ tokenAddress = worldConfig.blockchain?.worldTokenAddress;
655
+ spinner2.stop();
656
+ if (!tokenAddress) {
657
+ console.error(chalk2.red("\u274C No token address found"));
658
+ console.log(chalk2.yellow("\\nProvide address with --address or deploy token first\\n"));
659
+ process.exit(1);
660
+ }
661
+ }
662
+ console.log(` ${chalk2.gray("Contract:")} ${tokenAddress}\\n`);
663
+ const spinner = ora2("Verifying on Sourcify...").start();
664
+ try {
665
+ const worldCorePath = path3.join(process.cwd(), "../world-core");
666
+ const command = `MONAD_ETHERSCAN_KEY="" forge verify-contract ${tokenAddress} contracts/WorldToken.sol:WorldToken --chain 10143 --verifier sourcify --verifier-url https://sourcify-api-monad.blockvision.org/`;
667
+ execSync(command, {
668
+ cwd: worldCorePath,
669
+ stdio: "pipe"
670
+ });
671
+ spinner.succeed(chalk2.green("Contract verified successfully!"));
672
+ console.log(chalk2.gray("\\n Contract is now publicly verified on Sourcify\\n"));
673
+ } catch (error) {
674
+ spinner.fail(chalk2.red("Verification failed"));
675
+ console.error(chalk2.red("\\nError:"), error.message);
676
+ process.exit(1);
677
+ }
678
+ } catch (error) {
679
+ console.error(chalk2.red("Error:"), error.message);
680
+ process.exit(1);
681
+ }
682
+ });
683
+ tokenCommand.command("mint").description("Mint additional tokens").argument("<amount>", "Amount to mint").option("--to <address>", "Recipient address (defaults to deployer)").action(async (amount, options) => {
684
+ console.log(chalk2.bold.cyan("\\n\u{1FA99} Minting Tokens\\n"));
685
+ console.log(chalk2.yellow("\u26A0\uFE0F Token minting not yet implemented"));
686
+ console.log(chalk2.gray("\\n You can mint tokens by calling the contract directly\\n"));
687
+ });
688
+
689
+ // src/commands/start.ts
690
+ import { Command as Command3 } from "commander";
691
+ import chalk3 from "chalk";
692
+ import ora3 from "ora";
693
+ import { execSync as execSync2 } from "child_process";
694
+ var startCommand = new Command3("start").description("Start the world server").option("-p, --port <port>", "Server port", "4000").option("--dev", "Development mode with auto-reload").action(async (options) => {
695
+ console.log(chalk3.bold.cyan("\\n\u{1F30D} Starting World Server\\n"));
696
+ try {
697
+ const command = options.dev ? "npm run dev" : "npm start";
698
+ const spinner = ora3("Starting server...").start();
699
+ spinner.stop();
700
+ console.log(chalk3.gray(` Running: ${command}\\n`));
701
+ execSync2(command, {
702
+ cwd: process.cwd(),
703
+ stdio: "inherit"
704
+ });
705
+ } catch (error) {
706
+ console.error(chalk3.red("\\nError starting server:"), error.message);
707
+ process.exit(1);
708
+ }
709
+ });
710
+
711
+ // src/commands/deploy.ts
712
+ import { Command as Command4 } from "commander";
713
+ import chalk4 from "chalk";
714
+ import ora4 from "ora";
715
+ import { execSync as execSync3 } from "child_process";
716
+ import path4 from "path";
717
+ var deployCommand = new Command4("deploy").description("Deploy smart contracts to Monad").option("-r, --rpc-url <url>", "RPC URL (overrides .env)").action(async (options) => {
718
+ console.log(chalk4.bold.cyan("\\n\u{1F680} Deploying Smart Contracts\\n"));
719
+ try {
720
+ const spinner = ora4("Checking environment...").start();
721
+ const privateKey = process.env.DEPLOYER_PRIVATE_KEY;
722
+ if (!privateKey) {
723
+ spinner.fail();
724
+ console.error(chalk4.red("\u274C DEPLOYER_PRIVATE_KEY not found in .env file"));
725
+ console.log(chalk4.yellow("\\nPlease add your private key to .env:"));
726
+ console.log(chalk4.gray("DEPLOYER_PRIVATE_KEY=0x...\\n"));
727
+ process.exit(1);
728
+ }
729
+ const rpcUrl = options.rpcUrl || process.env.MONAD_RPC_URL || "https://rpc-testnet.monadinfra.com";
730
+ spinner.text = "Loading configuration...";
731
+ const worldConfig = await loadWorldConfig();
732
+ spinner.succeed();
733
+ console.log(chalk4.bold("\u{1F4CB} Deployment Details\\n"));
734
+ console.log(` ${chalk4.gray("World Name:")} ${worldConfig.name}`);
735
+ console.log(` ${chalk4.gray("Entry Fee:")} ${worldConfig.admission?.entryFee || 0} MON`);
736
+ console.log(` ${chalk4.gray("RPC URL:")} ${rpcUrl}\\n`);
737
+ const deploySpinner = ora4("Deploying AgentRegistry...").start();
738
+ try {
739
+ const worldCorePath = path4.join(process.cwd(), "../world-core");
740
+ const env = {
741
+ ...process.env,
742
+ MONAD_RPC_URL: rpcUrl,
743
+ DEPLOYER_PRIVATE_KEY: privateKey,
744
+ WORLD_NAME: worldConfig.name,
745
+ ENTRY_FEE_MON: (worldConfig.admission?.entryFee || 0).toString()
746
+ };
747
+ const command = `forge script script/Deploy.s.sol:DeployScript --rpc-url monad --broadcast -vvv`;
748
+ const output = execSync3(command, {
749
+ cwd: worldCorePath,
750
+ env,
751
+ encoding: "utf-8"
752
+ });
753
+ const agentRegistryMatch = output.match(/AgentRegistry deployed to: (0x[a-fA-F0-9]{40})/);
754
+ const membershipMatch = output.match(/WorldMembership deployed to: (0x[a-fA-F0-9]{40})/);
755
+ if (!agentRegistryMatch || !membershipMatch) {
756
+ throw new Error("Could not parse deployed contract addresses");
757
+ }
758
+ const agentRegistryAddress = agentRegistryMatch[1];
759
+ const membershipAddress = membershipMatch[1];
760
+ deploySpinner.succeed(chalk4.green("Contracts deployed successfully!"));
761
+ console.log(chalk4.bold("\\n\u2705 Deployment Complete\\n"));
762
+ console.log(` ${chalk4.gray("AgentRegistry:")} ${chalk4.green(agentRegistryAddress)}`);
763
+ console.log(` ${chalk4.gray("WorldMembership:")} ${chalk4.green(membershipAddress)}`);
764
+ const updateSpinner = ora4("\\nUpdating world.config.json...").start();
765
+ const fs4 = await import("fs/promises");
766
+ worldConfig.blockchain = worldConfig.blockchain || {};
767
+ worldConfig.blockchain.agentRegistryAddress = agentRegistryAddress;
768
+ worldConfig.blockchain.membershipContractAddress = membershipAddress;
769
+ await fs4.writeFile(
770
+ path4.join(process.cwd(), "world.config.json"),
771
+ JSON.stringify(worldConfig, null, 2)
772
+ );
773
+ updateSpinner.succeed(chalk4.green("Configuration updated"));
774
+ console.log(chalk4.bold("\\n\u{1F4DD} Next Steps:\\n"));
775
+ console.log(chalk4.gray(" 1. Add contract addresses to .env:"));
776
+ console.log(chalk4.gray(` AGENT_REGISTRY_ADDRESS=${agentRegistryAddress}`));
777
+ console.log(chalk4.gray(` WORLD_MEMBERSHIP_ADDRESS=${membershipAddress}`));
778
+ console.log(chalk4.gray(" 2. Start world: npm run dev\\n"));
779
+ } catch (error) {
780
+ deploySpinner.fail(chalk4.red("Deployment failed"));
781
+ console.error(chalk4.red("\\nError:"), error.message);
782
+ process.exit(1);
783
+ }
784
+ } catch (error) {
785
+ console.error(chalk4.red("Error:"), error.message);
786
+ process.exit(1);
787
+ }
788
+ });
789
+
790
+ // src/index.ts
791
+ var program = new Command5();
792
+ program.name("moltium-world").description("CLI tool for creating and managing Moltium World SDK projects").version("0.1.0");
793
+ program.addCommand(initCommand);
794
+ program.addCommand(tokenCommand);
795
+ program.addCommand(startCommand);
796
+ program.addCommand(deployCommand);
797
+ program.exitOverride();
798
+ try {
799
+ await program.parseAsync(process.argv);
800
+ } catch (error) {
801
+ if (error.code !== "commander.help" && error.code !== "commander.version") {
802
+ console.error(chalk5.red("Error:"), error.message);
803
+ process.exit(1);
804
+ }
805
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@moltium/world-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for creating and managing Moltium World SDK projects",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "ai",
8
+ "agent",
9
+ "world",
10
+ "simulation",
11
+ "cli",
12
+ "moltium",
13
+ "monad",
14
+ "blockchain"
15
+ ],
16
+ "type": "module",
17
+ "bin": {
18
+ "moltium-world": "dist/index.js"
19
+ },
20
+ "main": "./dist/index.js",
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "dev": "tsup --watch",
27
+ "test": "vitest run --passWithNoTests",
28
+ "test:watch": "vitest",
29
+ "clean": "rm -rf dist"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "dependencies": {
35
+ "@moltium/world-core": "^0.1.0",
36
+ "chalk": "^5.3.0",
37
+ "commander": "^12.1.0",
38
+ "dotenv": "^16.4.0",
39
+ "ethers": "^6.13.0",
40
+ "inquirer": "^12.2.0",
41
+ "ora": "^8.1.0",
42
+ "zod": "^3.24.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/inquirer": "^9.0.0",
46
+ "@types/node": "^22.0.0",
47
+ "tsup": "^8.3.0",
48
+ "typescript": "^5.3.0",
49
+ "vitest": "^2.1.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ }
54
+ }