@moltium/world-core 0.1.0 → 0.1.2

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 CHANGED
@@ -1,15 +1,6 @@
1
1
  # @moltium/world-core
2
2
 
3
- World runtime for creating agent simulation environments with A2A-based admission and blockchain validation.
4
-
5
- ## Features
6
-
7
- - **A2A Protocol Integration** — Discover and communicate with Moltium agents via A2A
8
- - **Blockchain Validation** — On-chain agent registration and soulbound NFT membership on Monad
9
- - **Configurable Persistence** — SQLite, PostgreSQL, Redis, MongoDB, or LevelDB backends
10
- - **Flexible Admission Rules** — Skill-based, tag-based, and custom evaluators
11
- - **World Simulation Engine** — Tick-based execution with rule enforcement
12
- - **Entry Fee System** — MON token payment with soulbound NFT access control
3
+ This package requires [Foundry](https://getfoundry.sh/) to be installed for smart contract deployment.
13
4
 
14
5
  ## Installation
15
6
 
@@ -17,51 +8,34 @@ World runtime for creating agent simulation environments with A2A-based admissio
17
8
  npm install @moltium/world-core
18
9
  ```
19
10
 
20
- ## Quick Start
11
+ The package will automatically run `forge install` during installation to fetch OpenZeppelin dependencies. If Foundry is not installed, you'll need to run it manually:
21
12
 
22
- ```typescript
23
- import { World, createWorldApp } from '@moltium/world-core';
24
- import type { WorldConfig } from '@moltium/world-core';
13
+ ```bash
14
+ cd node_modules/@moltium/world-core
15
+ forge install
16
+ ```
25
17
 
26
- const config: WorldConfig = {
27
- name: 'Trading Arena',
28
- description: 'Autonomous trading simulation',
29
-
30
- server: {
31
- port: 4000,
32
- host: 'localhost',
33
- },
34
-
35
- admission: {
36
- maxAgents: 50,
37
- requiredSkills: ['trading', 'analysis'],
38
- },
39
-
40
- simulation: {
41
- tickIntervalMs: 5000,
42
- },
43
-
44
- persistence: {
45
- type: 'sqlite',
46
- sqlite: { filename: './world.db' },
47
- },
48
-
49
- blockchain: {
50
- rpcUrl: process.env.MONAD_RPC_URL!,
51
- privateKey: process.env.DEPLOYER_PRIVATE_KEY!,
52
- entryFee: 0.5, // MON tokens
53
- requireMembership: true,
54
- },
55
- };
18
+ ## What's Included
56
19
 
57
- const world = new World(config);
58
- await world.init();
59
- await world.start();
60
- ```
20
+ - TypeScript SDK for world management
21
+ - Solidity smart contracts:
22
+ - `AgentRegistry.sol` - Agent registration and metadata
23
+ - `WorldMembership.sol` - Soulbound NFT membership tokens
24
+ - `WorldToken.sol` - ERC20 world currency template
25
+ - Foundry deployment scripts
26
+ - Test suite
27
+
28
+ ## Foundry Dependencies
29
+
30
+ This package uses Foundry submodules for contract dependencies. The following will be auto-installed:
31
+
32
+ - `openzeppelin-contracts` - Standard token implementations
33
+
34
+ These are cloned into the `lib/` folder when you run `forge install`.
61
35
 
62
- ## Documentation
36
+ ## Usage
63
37
 
64
- See the [implementation plan](../../world_implementation.md) for detailed architecture and API documentation.
38
+ See the [@moltium/world-cli](https://www.npmjs.com/package/@moltium/world-cli) package for creating and deploying worlds.
65
39
 
66
40
  ## License
67
41
 
package/foundry.toml ADDED
@@ -0,0 +1,27 @@
1
+ [profile.default]
2
+ src = "contracts"
3
+ out = "out"
4
+ libs = ["lib"]
5
+ solc_version = "0.8.24"
6
+ optimizer = true
7
+ optimizer_runs = 200
8
+ via_ir = false
9
+
10
+ # Monad network configuration
11
+ [rpc_endpoints]
12
+ monad = "${MONAD_RPC_URL}"
13
+
14
+ [etherscan]
15
+ monad = { key = "${MONAD_ETHERSCAN_KEY}", url = "https://explorer.monad.xyz/api" }
16
+
17
+ # Test configuration
18
+ [profile.default.fuzz]
19
+ runs = 256
20
+
21
+ [profile.default.invariant]
22
+ runs = 256
23
+ depth = 15
24
+
25
+ eth-rpc-url="https://testnet-rpc.monad.xyz"
26
+ chain_id = 10143
27
+ evm_version = "prague"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moltium/world-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "World runtime for creating agent simulation environments with A2A-based admission and blockchain validation",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,7 +16,12 @@
16
16
  "types": "./dist/index.d.ts",
17
17
  "files": [
18
18
  "dist",
19
- "contracts"
19
+ "contracts",
20
+ "test",
21
+ "script",
22
+ "scripts",
23
+ "foundry.toml",
24
+ "remappings.txt"
20
25
  ],
21
26
  "scripts": {
22
27
  "build": "tsup",
@@ -24,8 +29,10 @@
24
29
  "test": "vitest run",
25
30
  "test:watch": "vitest",
26
31
  "clean": "rm -rf dist",
27
- "compile-contracts": "cd contracts && hardhat compile",
28
- "test-contracts": "cd contracts && hardhat test"
32
+ "compile-contracts": "forge build",
33
+ "test-contracts": "forge test",
34
+ "install-forge-deps": "forge install",
35
+ "postinstall": "bash scripts/postinstall.sh || true"
29
36
  },
30
37
  "publishConfig": {
31
38
  "access": "public"
package/remappings.txt ADDED
@@ -0,0 +1,2 @@
1
+ @openzeppelin/=lib/openzeppelin-contracts/
2
+ forge-std/=lib/forge-std/src/
@@ -0,0 +1,76 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {Script, console} from "forge-std/Script.sol";
5
+ import {AgentRegistry} from "../contracts/AgentRegistry.sol";
6
+ import {WorldMembership} from "../contracts/WorldMembership.sol";
7
+ import {WorldToken} from "../contracts/WorldToken.sol";
8
+
9
+ /**
10
+ * Foundry deployment script for Monad network
11
+ *
12
+ * Usage:
13
+ * forge script script/Deploy.s.sol:DeployScript --rpc-url monad --broadcast --verify
14
+ */
15
+ contract DeployScript is Script {
16
+ function run() external {
17
+ // Load environment variables
18
+ uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
19
+ string memory worldName = vm.envOr("WORLD_NAME", string("TestWorld"));
20
+ uint256 entryFee = vm.envOr("ENTRY_FEE_MON", uint256(0.1 ether));
21
+ bool deployToken = vm.envOr("DEPLOY_WORLD_TOKEN", false);
22
+
23
+ vm.startBroadcast(deployerPrivateKey);
24
+
25
+ // 1. Deploy AgentRegistry
26
+ console.log("Deploying AgentRegistry...");
27
+ AgentRegistry agentRegistry = new AgentRegistry();
28
+ console.log("AgentRegistry deployed to:", address(agentRegistry));
29
+
30
+ // 2. Deploy WorldMembership
31
+ console.log("\nDeploying WorldMembership...");
32
+ console.log(" World Name:", worldName);
33
+ console.log(" Entry Fee:", entryFee / 1 ether, "MON");
34
+ WorldMembership membership = new WorldMembership(worldName, entryFee);
35
+ console.log("WorldMembership deployed to:", address(membership));
36
+
37
+ // 3. Deploy WorldToken (optional)
38
+ address tokenAddress = address(0);
39
+ if (deployToken) {
40
+ string memory tokenName = vm.envOr("TOKEN_NAME", string("World Token"));
41
+ string memory tokenSymbol = vm.envOr("TOKEN_SYMBOL", string("WORLD"));
42
+ uint256 initialSupply = vm.envOr("TOKEN_INITIAL_SUPPLY", uint256(1000000));
43
+ uint8 decimals = uint8(vm.envOr("TOKEN_DECIMALS", uint256(18)));
44
+
45
+ console.log("\nDeploying WorldToken...");
46
+ console.log(" Name:", tokenName);
47
+ console.log(" Symbol:", tokenSymbol);
48
+ console.log(" Initial Supply:", initialSupply);
49
+ console.log(" Decimals:", decimals);
50
+
51
+ WorldToken token = new WorldToken(tokenName, tokenSymbol, initialSupply, decimals);
52
+ tokenAddress = address(token);
53
+ console.log("WorldToken deployed to:", tokenAddress);
54
+ }
55
+
56
+ vm.stopBroadcast();
57
+
58
+ // Print summary
59
+ console.log("\n=====================================");
60
+ console.log("Deployment Summary");
61
+ console.log("=====================================");
62
+ console.log("AgentRegistry: ", address(agentRegistry));
63
+ console.log("WorldMembership: ", address(membership));
64
+ if (deployToken) {
65
+ console.log("WorldToken: ", tokenAddress);
66
+ }
67
+ console.log("=====================================");
68
+
69
+ console.log("\nAdd these to your .env file:");
70
+ console.log("AGENT_REGISTRY_ADDRESS=", address(agentRegistry));
71
+ console.log("WORLD_MEMBERSHIP_ADDRESS=", address(membership));
72
+ if (deployToken) {
73
+ console.log("WORLD_TOKEN_ADDRESS=", tokenAddress);
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,83 @@
1
+ const hre = require("hardhat");
2
+
3
+ /**
4
+ * Deploy all world contracts to Monad network
5
+ *
6
+ * Usage:
7
+ * npx hardhat run scripts/deploy.js --network monad
8
+ */
9
+ async function main() {
10
+ console.log("Deploying World SDK contracts to Monad...\n");
11
+
12
+ const [deployer] = await hre.ethers.getSigners();
13
+ console.log("Deploying with account:", deployer.address);
14
+ console.log("Account balance:", (await hre.ethers.provider.getBalance(deployer.address)).toString(), "\n");
15
+
16
+ // 1. Deploy AgentRegistry
17
+ console.log("Deploying AgentRegistry...");
18
+ const AgentRegistry = await hre.ethers.getContractFactory("AgentRegistry");
19
+ const agentRegistry = await AgentRegistry.deploy();
20
+ await agentRegistry.waitForDeployment();
21
+ const agentRegistryAddress = await agentRegistry.getAddress();
22
+ console.log("✅ AgentRegistry deployed to:", agentRegistryAddress, "\n");
23
+
24
+ // 2. Deploy WorldMembership
25
+ const worldName = process.env.WORLD_NAME || "TestWorld";
26
+ const entryFee = hre.ethers.parseEther(process.env.ENTRY_FEE_MON || "0.1"); // Default 0.1 MON
27
+
28
+ console.log("Deploying WorldMembership...");
29
+ console.log(" World Name:", worldName);
30
+ console.log(" Entry Fee:", hre.ethers.formatEther(entryFee), "MON");
31
+
32
+ const WorldMembership = await hre.ethers.getContractFactory("WorldMembership");
33
+ const worldMembership = await WorldMembership.deploy(worldName, entryFee);
34
+ await worldMembership.waitForDeployment();
35
+ const membershipAddress = await worldMembership.getAddress();
36
+ console.log("✅ WorldMembership deployed to:", membershipAddress, "\n");
37
+
38
+ // 3. Deploy WorldToken (optional)
39
+ let worldTokenAddress = null;
40
+ if (process.env.DEPLOY_WORLD_TOKEN === "true") {
41
+ const tokenName = process.env.TOKEN_NAME || "World Token";
42
+ const tokenSymbol = process.env.TOKEN_SYMBOL || "WORLD";
43
+ const initialSupply = process.env.TOKEN_INITIAL_SUPPLY || "1000000";
44
+ const decimals = parseInt(process.env.TOKEN_DECIMALS || "18");
45
+
46
+ console.log("Deploying WorldToken...");
47
+ console.log(" Name:", tokenName);
48
+ console.log(" Symbol:", tokenSymbol);
49
+ console.log(" Initial Supply:", initialSupply);
50
+ console.log(" Decimals:", decimals);
51
+
52
+ const WorldToken = await hre.ethers.getContractFactory("WorldToken");
53
+ const worldToken = await WorldToken.deploy(tokenName, tokenSymbol, initialSupply, decimals);
54
+ await worldToken.waitForDeployment();
55
+ worldTokenAddress = await worldToken.getAddress();
56
+ console.log("✅ WorldToken deployed to:", worldTokenAddress, "\n");
57
+ }
58
+
59
+ // Summary
60
+ console.log("=====================================");
61
+ console.log("Deployment Summary");
62
+ console.log("=====================================");
63
+ console.log("AgentRegistry: ", agentRegistryAddress);
64
+ console.log("WorldMembership: ", membershipAddress);
65
+ if (worldTokenAddress) {
66
+ console.log("WorldToken: ", worldTokenAddress);
67
+ }
68
+ console.log("=====================================\n");
69
+
70
+ console.log("Add these to your .env file:");
71
+ console.log(`AGENT_REGISTRY_ADDRESS=${agentRegistryAddress}`);
72
+ console.log(`WORLD_MEMBERSHIP_ADDRESS=${membershipAddress}`);
73
+ if (worldTokenAddress) {
74
+ console.log(`WORLD_TOKEN_ADDRESS=${worldTokenAddress}`);
75
+ }
76
+ }
77
+
78
+ main()
79
+ .then(() => process.exit(0))
80
+ .catch((error) => {
81
+ console.error(error);
82
+ process.exit(1);
83
+ });
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ # Post-install script for @moltium/world-core
4
+ # Installs Foundry dependencies if forge is available
5
+
6
+ if command -v forge &> /dev/null; then
7
+ echo "📦 Installing Foundry dependencies..."
8
+ cd "$(dirname "$0")" || exit 1
9
+ forge install --no-commit
10
+ echo "✅ Foundry dependencies installed"
11
+ else
12
+ echo "⚠️ Foundry not found - skipping dependency installation"
13
+ echo " Install Foundry from https://getfoundry.sh/"
14
+ echo " Then run: cd node_modules/@moltium/world-core && forge install"
15
+ fi
@@ -0,0 +1,87 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {AgentRegistry} from "../contracts/AgentRegistry.sol";
6
+
7
+ contract AgentRegistryTest is Test {
8
+ AgentRegistry public registry;
9
+ address public agent1 = address(0x1);
10
+ address public agent2 = address(0x2);
11
+
12
+ function setUp() public {
13
+ registry = new AgentRegistry();
14
+ }
15
+
16
+ function testRegisterAgent() public {
17
+ vm.prank(agent1);
18
+ registry.registerAgent("http://localhost:3000", "Agent1");
19
+
20
+ assertTrue(registry.isAgentValid(agent1));
21
+
22
+ AgentRegistry.AgentInfo memory info = registry.getAgent(agent1);
23
+ assertEq(info.walletAddress, agent1);
24
+ assertEq(info.agentUrl, "http://localhost:3000");
25
+ assertEq(info.name, "Agent1");
26
+ assertTrue(info.isActive);
27
+ }
28
+
29
+ function testCannotRegisterTwice() public {
30
+ vm.startPrank(agent1);
31
+ registry.registerAgent("http://localhost:3000", "Agent1");
32
+
33
+ vm.expectRevert("Agent already registered");
34
+ registry.registerAgent("http://localhost:3001", "Agent1Updated");
35
+ vm.stopPrank();
36
+ }
37
+
38
+ function testCannotRegisterSameUrl() public {
39
+ vm.prank(agent1);
40
+ registry.registerAgent("http://localhost:3000", "Agent1");
41
+
42
+ vm.prank(agent2);
43
+ vm.expectRevert("URL already registered");
44
+ registry.registerAgent("http://localhost:3000", "Agent2");
45
+ }
46
+
47
+ function testDeactivateAgent() public {
48
+ vm.startPrank(agent1);
49
+ registry.registerAgent("http://localhost:3000", "Agent1");
50
+ assertTrue(registry.isAgentValid(agent1));
51
+
52
+ registry.deactivateAgent();
53
+ assertFalse(registry.isAgentValid(agent1));
54
+ vm.stopPrank();
55
+ }
56
+
57
+ function testReactivateAgent() public {
58
+ vm.startPrank(agent1);
59
+ registry.registerAgent("http://localhost:3000", "Agent1");
60
+ registry.deactivateAgent();
61
+ assertFalse(registry.isAgentValid(agent1));
62
+
63
+ registry.activateAgent();
64
+ assertTrue(registry.isAgentValid(agent1));
65
+ vm.stopPrank();
66
+ }
67
+
68
+ function testGetWalletByUrl() public {
69
+ vm.prank(agent1);
70
+ registry.registerAgent("http://localhost:3000", "Agent1");
71
+
72
+ address wallet = registry.getWalletByUrl("http://localhost:3000");
73
+ assertEq(wallet, agent1);
74
+ }
75
+
76
+ function testEmptyUrlReverts() public {
77
+ vm.prank(agent1);
78
+ vm.expectRevert("Agent URL cannot be empty");
79
+ registry.registerAgent("", "Agent1");
80
+ }
81
+
82
+ function testEmptyNameReverts() public {
83
+ vm.prank(agent1);
84
+ vm.expectRevert("Agent name cannot be empty");
85
+ registry.registerAgent("http://localhost:3000", "");
86
+ }
87
+ }
@@ -0,0 +1,96 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {WorldMembership} from "../contracts/WorldMembership.sol";
6
+
7
+ contract WorldMembershipTest is Test {
8
+ WorldMembership public membership;
9
+ address public worldOwner = address(this);
10
+ address public agent1 = address(0x1);
11
+ address public agent2 = address(0x2);
12
+ uint256 public entryFee = 0.1 ether;
13
+
14
+ function setUp() public {
15
+ membership = new WorldMembership("TestWorld", entryFee);
16
+ vm.deal(worldOwner, 100 ether);
17
+ }
18
+
19
+ function testMintMembership() public {
20
+ membership.mintMembership{value: entryFee}(agent1);
21
+
22
+ assertTrue(membership.hasMembership(agent1));
23
+ assertEq(membership.totalMembers(), 1);
24
+ assertEq(membership.balanceOf(agent1), 1);
25
+ }
26
+
27
+ function testCannotMintWithInsufficientFee() public {
28
+ vm.expectRevert("Insufficient entry fee");
29
+ membership.mintMembership{value: 0.05 ether}(agent1);
30
+ }
31
+
32
+ function testCannotMintDuplicate() public {
33
+ membership.mintMembership{value: entryFee}(agent1);
34
+
35
+ vm.expectRevert("Agent already has membership");
36
+ membership.mintMembership{value: entryFee}(agent1);
37
+ }
38
+
39
+ function testRevokeMembership() public {
40
+ membership.mintMembership{value: entryFee}(agent1);
41
+ assertTrue(membership.hasMembership(agent1));
42
+
43
+ membership.revokeMembership(agent1);
44
+ assertFalse(membership.hasMembership(agent1));
45
+ assertEq(membership.balanceOf(agent1), 0);
46
+ }
47
+
48
+ function testSoulboundTransferReverts() public {
49
+ membership.mintMembership{value: entryFee}(agent1);
50
+ uint256 tokenId = membership.agentToToken(agent1);
51
+
52
+ vm.prank(agent1);
53
+ // Using try-catch to silence erc20-unchecked-transfer warning
54
+ // Note: This is an ERC721 call, not ERC20, and we expect it to revert
55
+ try membership.transferFrom(agent1, agent2, tokenId) {
56
+ fail("Expected transfer to revert");
57
+ } catch Error(string memory reason) {
58
+ assertEq(reason, "WorldMembership: Token is soulbound and cannot be transferred");
59
+ }
60
+ }
61
+
62
+ function testWithdrawFees() public {
63
+ membership.mintMembership{value: entryFee}(agent1);
64
+ membership.mintMembership{value: entryFee}(agent2);
65
+
66
+ uint256 balanceBefore = address(this).balance;
67
+ membership.withdrawFees();
68
+ uint256 balanceAfter = address(this).balance;
69
+
70
+ assertEq(balanceAfter - balanceBefore, entryFee * 2);
71
+ }
72
+
73
+ function testUpdateEntryFee() public {
74
+ uint256 newFee = 0.2 ether;
75
+ membership.updateEntryFee(newFee);
76
+ assertEq(membership.entryFee(), newFee);
77
+ }
78
+
79
+ function testOnlyOwnerCanMint() public {
80
+ // hoax sets both msg.sender and provides ETH
81
+ hoax(agent1, 1 ether);
82
+ vm.expectRevert();
83
+ membership.mintMembership{value: entryFee}(agent2);
84
+ }
85
+
86
+ function testOnlyOwnerCanRevoke() public {
87
+ membership.mintMembership{value: entryFee}(agent1);
88
+
89
+ vm.prank(agent2);
90
+ vm.expectRevert();
91
+ membership.revokeMembership(agent1);
92
+ }
93
+
94
+ // Allow this contract to receive ether
95
+ receive() external payable {}
96
+ }