@moltium/world-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @moltium/world-core
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
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @moltium/world-core
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { World, createWorldApp } from '@moltium/world-core';
24
+ import type { WorldConfig } from '@moltium/world-core';
25
+
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
+ };
56
+
57
+ const world = new World(config);
58
+ await world.init();
59
+ await world.start();
60
+ ```
61
+
62
+ ## Documentation
63
+
64
+ See the [implementation plan](../../world_implementation.md) for detailed architecture and API documentation.
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,98 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ /**
5
+ * @title AgentRegistry
6
+ * @notice On-chain registry for validating Moltium agents
7
+ * @dev Allows world owners to verify agent authenticity before admission
8
+ */
9
+ contract AgentRegistry {
10
+ struct AgentInfo {
11
+ address walletAddress;
12
+ string agentUrl;
13
+ string name;
14
+ uint256 registeredAt;
15
+ bool isActive;
16
+ }
17
+
18
+ // Agent wallet => AgentInfo
19
+ mapping(address => AgentInfo) public agents;
20
+
21
+ // Agent URL => wallet (for reverse lookup)
22
+ mapping(string => address) public urlToWallet;
23
+
24
+ // Events
25
+ event AgentRegistered(address indexed wallet, string agentUrl, string name);
26
+ event AgentDeactivated(address indexed wallet);
27
+ event AgentActivated(address indexed wallet);
28
+
29
+ /**
30
+ * @notice Register a new agent
31
+ * @param agentUrl The agent's A2A URL
32
+ * @param name The agent's name
33
+ */
34
+ function registerAgent(string memory agentUrl, string memory name) external {
35
+ require(bytes(agentUrl).length > 0, "Agent URL cannot be empty");
36
+ require(bytes(name).length > 0, "Agent name cannot be empty");
37
+ require(!agents[msg.sender].isActive, "Agent already registered");
38
+ require(urlToWallet[agentUrl] == address(0), "URL already registered");
39
+
40
+ agents[msg.sender] = AgentInfo({
41
+ walletAddress: msg.sender,
42
+ agentUrl: agentUrl,
43
+ name: name,
44
+ registeredAt: block.timestamp,
45
+ isActive: true
46
+ });
47
+
48
+ urlToWallet[agentUrl] = msg.sender;
49
+
50
+ emit AgentRegistered(msg.sender, agentUrl, name);
51
+ }
52
+
53
+ /**
54
+ * @notice Deactivate an agent (can be called by agent owner)
55
+ */
56
+ function deactivateAgent() external {
57
+ require(agents[msg.sender].isActive, "Agent not active");
58
+ agents[msg.sender].isActive = false;
59
+ emit AgentDeactivated(msg.sender);
60
+ }
61
+
62
+ /**
63
+ * @notice Reactivate an agent
64
+ */
65
+ function activateAgent() external {
66
+ require(!agents[msg.sender].isActive, "Agent already active");
67
+ require(agents[msg.sender].registeredAt > 0, "Agent not registered");
68
+ agents[msg.sender].isActive = true;
69
+ emit AgentActivated(msg.sender);
70
+ }
71
+
72
+ /**
73
+ * @notice Check if an agent is registered and active
74
+ * @param wallet The agent's wallet address
75
+ * @return bool True if agent is registered and active
76
+ */
77
+ function isAgentValid(address wallet) external view returns (bool) {
78
+ return agents[wallet].isActive;
79
+ }
80
+
81
+ /**
82
+ * @notice Get agent info by wallet
83
+ * @param wallet The agent's wallet address
84
+ * @return AgentInfo The agent's information
85
+ */
86
+ function getAgent(address wallet) external view returns (AgentInfo memory) {
87
+ return agents[wallet];
88
+ }
89
+
90
+ /**
91
+ * @notice Get agent wallet by URL
92
+ * @param agentUrl The agent's A2A URL
93
+ * @return address The agent's wallet address
94
+ */
95
+ function getWalletByUrl(string memory agentUrl) external view returns (address) {
96
+ return urlToWallet[agentUrl];
97
+ }
98
+ }
@@ -0,0 +1,157 @@
1
+ # Smart Contracts - Moltium World SDK
2
+
3
+ This directory contains the Solidity smart contracts for blockchain integration with Monad network.
4
+
5
+ ## Contracts
6
+
7
+ ### AgentRegistry.sol
8
+ **Purpose:** On-chain registry for validating Moltium agents
9
+
10
+ **Features:**
11
+ - Register agents with wallet address, URL, and name
12
+ - Activate/deactivate agent status
13
+ - Lookup agents by wallet or URL
14
+ - Validate agent before world admission
15
+
16
+ **Key Methods:**
17
+ ```solidity
18
+ function registerAgent(string agentUrl, string name) external
19
+ function isAgentValid(address wallet) external view returns (bool)
20
+ function getAgent(address wallet) external view returns (AgentInfo)
21
+ ```
22
+
23
+ ### WorldMembership.sol
24
+ **Purpose:** Soulbound NFT representing membership in a world
25
+
26
+ **Features:**
27
+ - ERC-721 compliant but non-transferable (soulbound)
28
+ - Entry fee payment in MON tokens
29
+ - Mint membership after fee payment
30
+ - Revoke membership (world owner only)
31
+ - World owner can withdraw collected fees
32
+
33
+ **Key Methods:**
34
+ ```solidity
35
+ function mintMembership(address agent) external payable onlyOwner
36
+ function revokeMembership(address agent) external onlyOwner
37
+ function hasMembership(address agent) external view returns (bool)
38
+ function withdrawFees() external onlyOwner
39
+ ```
40
+
41
+ **Soulbound Implementation:**
42
+ - Overrides `_update()` to prevent transfers
43
+ - Only allows minting (from `address(0)`) and burning (to `address(0)`)
44
+ - All other transfers revert with error
45
+
46
+ ### WorldToken.sol
47
+ **Purpose:** Optional ERC-20 token for world-specific economies
48
+
49
+ **Features:**
50
+ - Standard ERC-20 with configurable decimals
51
+ - Mint/burn capabilities (world owner only)
52
+ - Can be used instead of MON for in-world transactions
53
+
54
+ **Key Methods:**
55
+ ```solidity
56
+ function mint(address to, uint256 amount) external onlyOwner
57
+ function burn(address from, uint256 amount) external onlyOwner
58
+ ```
59
+
60
+ ## Deployment
61
+
62
+ ### Prerequisites
63
+ 1. Install Foundry:
64
+ ```bash
65
+ curl -L https://foundry.paradigm.xyz | bash
66
+ foundryup
67
+ ```
68
+
69
+ 2. Set up environment variables:
70
+ ```bash
71
+ cp .env.example .env
72
+ # Edit .env with your values
73
+ ```
74
+
75
+ ### Deploy to Monad Testnet
76
+
77
+ ```bash
78
+ # Build contracts
79
+ forge build
80
+
81
+ # Deploy to Monad
82
+ forge script script/Deploy.s.sol:DeployScript --rpc-url monad --broadcast --verify
83
+ ```
84
+
85
+ This will deploy:
86
+ 1. AgentRegistry
87
+ 2. WorldMembership (with configured entry fee)
88
+ 3. WorldToken (if `DEPLOY_WORLD_TOKEN=true`)
89
+
90
+ ### Environment Variables
91
+
92
+ ```bash
93
+ # Network
94
+ MONAD_RPC_URL=https://testnet-rpc.monad.xyz
95
+ DEPLOYER_PRIVATE_KEY=0x...
96
+
97
+ # World Config
98
+ WORLD_NAME=MyWorld
99
+ ENTRY_FEE_MON=100000000000000000 # 0.1 MON in wei
100
+
101
+ # Optional Token
102
+ DEPLOY_WORLD_TOKEN=false
103
+ TOKEN_NAME=World Token
104
+ TOKEN_SYMBOL=WORLD
105
+ TOKEN_INITIAL_SUPPLY=1000000
106
+ TOKEN_DECIMALS=18
107
+ ```
108
+
109
+ ## Testing
110
+
111
+ Run Foundry tests:
112
+ ```bash
113
+ # Run all tests
114
+ forge test
115
+
116
+ # Run with verbosity
117
+ forge test -vv
118
+
119
+ # Run specific test
120
+ forge test --match-test testMintMembership -vvv
121
+
122
+ # Run with gas reporting
123
+ forge test --gas-report
124
+ ```
125
+
126
+ ## Usage with World SDK
127
+
128
+ The contracts are automatically integrated via `BlockchainClient`:
129
+
130
+ ```typescript
131
+ import { World } from '@moltium/world-core';
132
+
133
+ const config = {
134
+ name: 'My World',
135
+ blockchain: {
136
+ rpcUrl: 'https://testnet-rpc.monad.xyz',
137
+ privateKey: process.env.DEPLOYER_PRIVATE_KEY,
138
+ entryFee: 0.1,
139
+ requireMembership: true,
140
+ enforceOnChainValidation: false,
141
+ agentRegistryAddress: '0x...',
142
+ membershipContractAddress: '0x...',
143
+ },
144
+ // ... other config
145
+ };
146
+
147
+ const world = new World(config);
148
+ await world.init(); // Connects to contracts
149
+ ```
150
+
151
+ ## Security Considerations
152
+
153
+ 1. **Private Keys**: Never commit private keys. Use `.env` and `.gitignore`
154
+ 2. **Entry Fees**: Start with low fees for testing
155
+ 3. **Ownership**: World owner has full control over membership
156
+ 4. **Soulbound**: Memberships cannot be transferred or sold
157
+ 5. **Gas Costs**: Entry fee covers gas + world revenue
@@ -0,0 +1,142 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
5
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
6
+
7
+ /**
8
+ * @title WorldMembership
9
+ * @notice Soulbound NFT representing membership in a Moltium world
10
+ * @dev Non-transferable NFT minted after entry fee payment
11
+ */
12
+ contract WorldMembership is ERC721, Ownable {
13
+ uint256 private _nextTokenId;
14
+
15
+ // World configuration
16
+ string public worldName;
17
+ uint256 public entryFee;
18
+
19
+ // Token ID => agent wallet
20
+ mapping(uint256 => address) public tokenToAgent;
21
+
22
+ // Agent wallet => token ID (for reverse lookup)
23
+ mapping(address => uint256) public agentToToken;
24
+
25
+ // Agent wallet => membership active status
26
+ mapping(address => bool) public isMember;
27
+
28
+ // Events
29
+ event MembershipMinted(address indexed agent, uint256 indexed tokenId, uint256 feePaid);
30
+ event MembershipRevoked(address indexed agent, uint256 indexed tokenId);
31
+ event EntryFeeUpdated(uint256 oldFee, uint256 newFee);
32
+
33
+ /**
34
+ * @notice Constructor
35
+ * @param _worldName The name of the world
36
+ * @param _entryFee Entry fee in MON (wei)
37
+ */
38
+ constructor(
39
+ string memory _worldName,
40
+ uint256 _entryFee
41
+ ) ERC721(
42
+ string(abi.encodePacked(_worldName, " Membership")),
43
+ string(abi.encodePacked("WORLD-", _worldName))
44
+ ) Ownable(msg.sender) {
45
+ worldName = _worldName;
46
+ entryFee = _entryFee;
47
+ }
48
+
49
+ /**
50
+ * @notice Mint membership NFT (called by world owner after entry fee payment)
51
+ * @param agent The agent's wallet address
52
+ */
53
+ function mintMembership(address agent) external payable onlyOwner {
54
+ require(msg.value >= entryFee, "Insufficient entry fee");
55
+ require(!isMember[agent], "Agent already has membership");
56
+ require(agent != address(0), "Invalid agent address");
57
+
58
+ uint256 tokenId = _nextTokenId++;
59
+ _safeMint(agent, tokenId);
60
+
61
+ tokenToAgent[tokenId] = agent;
62
+ agentToToken[agent] = tokenId;
63
+ isMember[agent] = true;
64
+
65
+ emit MembershipMinted(agent, tokenId, msg.value);
66
+ }
67
+
68
+ /**
69
+ * @notice Revoke membership (world owner can remove agents)
70
+ * @param agent The agent's wallet address
71
+ */
72
+ function revokeMembership(address agent) external onlyOwner {
73
+ require(isMember[agent], "Agent is not a member");
74
+
75
+ uint256 tokenId = agentToToken[agent];
76
+ _burn(tokenId);
77
+
78
+ isMember[agent] = false;
79
+ delete tokenToAgent[tokenId];
80
+ delete agentToToken[agent];
81
+
82
+ emit MembershipRevoked(agent, tokenId);
83
+ }
84
+
85
+ /**
86
+ * @notice Update entry fee (world owner only)
87
+ * @param newFee New entry fee in MON (wei)
88
+ */
89
+ function updateEntryFee(uint256 newFee) external onlyOwner {
90
+ uint256 oldFee = entryFee;
91
+ entryFee = newFee;
92
+ emit EntryFeeUpdated(oldFee, newFee);
93
+ }
94
+
95
+ /**
96
+ * @notice Withdraw collected entry fees (world owner only)
97
+ */
98
+ function withdrawFees() external onlyOwner {
99
+ uint256 balance = address(this).balance;
100
+ require(balance > 0, "No fees to withdraw");
101
+
102
+ (bool success, ) = payable(owner()).call{value: balance}("");
103
+ require(success, "Withdrawal failed");
104
+ }
105
+
106
+ /**
107
+ * @notice Override transfer to make NFT soulbound (non-transferable)
108
+ */
109
+ function _update(
110
+ address to,
111
+ uint256 tokenId,
112
+ address auth
113
+ ) internal virtual override returns (address) {
114
+ address from = _ownerOf(tokenId);
115
+
116
+ // Allow minting (from == address(0))
117
+ // Allow burning (to == address(0))
118
+ // Disallow transfers (from != address(0) && to != address(0))
119
+ if (from != address(0) && to != address(0)) {
120
+ revert("WorldMembership: Token is soulbound and cannot be transferred");
121
+ }
122
+
123
+ return super._update(to, tokenId, auth);
124
+ }
125
+
126
+ /**
127
+ * @notice Check if an address has active membership
128
+ * @param agent The agent's wallet address
129
+ * @return bool True if agent has active membership
130
+ */
131
+ function hasMembership(address agent) external view returns (bool) {
132
+ return isMember[agent];
133
+ }
134
+
135
+ /**
136
+ * @notice Get total number of members
137
+ * @return uint256 Total members
138
+ */
139
+ function totalMembers() external view returns (uint256) {
140
+ return _nextTokenId;
141
+ }
142
+ }
@@ -0,0 +1,56 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
6
+
7
+ /**
8
+ * @title WorldToken
9
+ * @notice Optional ERC20 token for world-specific economies
10
+ * @dev Can be used instead of MON for in-world transactions
11
+ */
12
+ contract WorldToken is ERC20, Ownable {
13
+ uint8 private _decimals;
14
+
15
+ /**
16
+ * @notice Constructor
17
+ * @param name Token name
18
+ * @param symbol Token symbol
19
+ * @param initialSupply Initial supply (will be minted to deployer)
20
+ * @param tokenDecimals Number of decimals
21
+ */
22
+ constructor(
23
+ string memory name,
24
+ string memory symbol,
25
+ uint256 initialSupply,
26
+ uint8 tokenDecimals
27
+ ) ERC20(name, symbol) Ownable(msg.sender) {
28
+ _decimals = tokenDecimals;
29
+ _mint(msg.sender, initialSupply * (10 ** tokenDecimals));
30
+ }
31
+
32
+ /**
33
+ * @notice Mint new tokens (world owner only)
34
+ * @param to Recipient address
35
+ * @param amount Amount to mint
36
+ */
37
+ function mint(address to, uint256 amount) external onlyOwner {
38
+ _mint(to, amount);
39
+ }
40
+
41
+ /**
42
+ * @notice Burn tokens (world owner only)
43
+ * @param from Address to burn from
44
+ * @param amount Amount to burn
45
+ */
46
+ function burn(address from, uint256 amount) external onlyOwner {
47
+ _burn(from, amount);
48
+ }
49
+
50
+ /**
51
+ * @notice Override decimals
52
+ */
53
+ function decimals() public view virtual override returns (uint8) {
54
+ return _decimals;
55
+ }
56
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@moltium/world-core",
3
+ "version": "0.1.0",
4
+ "description": "World runtime for creating agent simulation environments with A2A-based admission and blockchain validation",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.mjs",
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "contracts"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "clean": "rm -rf dist",
27
+ "compile-contracts": "cd contracts && hardhat compile",
28
+ "test-contracts": "cd contracts && hardhat test"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@moltium/core": "^0.1.20",
35
+ "@a2a-js/sdk": "^0.3.0",
36
+ "express": "^4.21.0",
37
+ "winston": "^3.17.0",
38
+ "uuid": "^11.0.3",
39
+ "zod": "^3.24.0",
40
+ "ethers": "^6.13.0"
41
+ },
42
+ "peerDependencies": {
43
+ "better-sqlite3": "^11.0.0",
44
+ "level": "^8.0.0",
45
+ "ioredis": "^5.0.0",
46
+ "pg": "^8.11.0",
47
+ "mongodb": "^6.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "better-sqlite3": {
51
+ "optional": true
52
+ },
53
+ "level": {
54
+ "optional": true
55
+ },
56
+ "ioredis": {
57
+ "optional": true
58
+ },
59
+ "pg": {
60
+ "optional": true
61
+ },
62
+ "mongodb": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "@types/express": "^5.0.0",
68
+ "@types/node": "^22.0.0",
69
+ "@types/better-sqlite3": "^7.6.0",
70
+ "hardhat": "^2.22.0",
71
+ "@nomicfoundation/hardhat-toolbox": "^5.0.0",
72
+ "@openzeppelin/contracts": "^5.0.0",
73
+ "tsup": "^8.3.0",
74
+ "typescript": "^5.3.0",
75
+ "vitest": "^2.1.0"
76
+ },
77
+ "engines": {
78
+ "node": ">=18.0.0"
79
+ }
80
+ }