@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.
- package/README.md +205 -0
- package/dist/index.js +805 -0
- 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
|
+
}
|