@moltium/world-core 0.1.21 → 0.1.23

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/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createLogger,
4
4
  logger,
5
5
  normalizeAgentUrl
6
- } from "./chunk-JW3PZRCW.js";
6
+ } from "./chunk-Y563WZWH.js";
7
7
 
8
8
  // src/config/types.ts
9
9
  function profileFromCard(card, walletAddress) {
@@ -766,7 +766,7 @@ var BlockchainClient = class {
766
766
  return this.wallet.address;
767
767
  }
768
768
  /**
769
- * Get entry fee in MON
769
+ * Get entry fee in MON (formatted)
770
770
  */
771
771
  async getEntryFee() {
772
772
  if (!this.membershipContract) {
@@ -780,6 +780,78 @@ var BlockchainClient = class {
780
780
  return "0";
781
781
  }
782
782
  }
783
+ /**
784
+ * Get entry fee in wei (raw BigInt string)
785
+ */
786
+ async getEntryFeeWei() {
787
+ if (!this.membershipContract) {
788
+ return "0";
789
+ }
790
+ try {
791
+ const fee = await this.membershipContract.entryFee();
792
+ return fee.toString();
793
+ } catch (error) {
794
+ logger3.error("Failed to get entry fee (wei)", { error: error.message });
795
+ return "0";
796
+ }
797
+ }
798
+ // Track used payment tx hashes to prevent replay attacks
799
+ usedPayments = /* @__PURE__ */ new Set();
800
+ /**
801
+ * Verify a payment transaction from an agent.
802
+ * Checks: tx confirmed, correct sender, correct recipient (world wallet), sufficient amount.
803
+ * Prevents replay attacks by tracking used tx hashes.
804
+ */
805
+ async verifyPayment(txHash, expectedSender, expectedAmountWei) {
806
+ const normalizedHash = txHash.toLowerCase();
807
+ if (this.usedPayments.has(normalizedHash)) {
808
+ logger3.warn("Payment verification: transaction already used", { txHash });
809
+ return false;
810
+ }
811
+ try {
812
+ const receipt = await this.provider.getTransactionReceipt(txHash);
813
+ if (!receipt || receipt.status !== 1) {
814
+ logger3.warn("Payment verification: transaction not confirmed or failed", { txHash });
815
+ return false;
816
+ }
817
+ const tx = await this.provider.getTransaction(txHash);
818
+ if (!tx) {
819
+ logger3.warn("Payment verification: transaction not found", { txHash });
820
+ return false;
821
+ }
822
+ if (tx.from.toLowerCase() !== expectedSender.toLowerCase()) {
823
+ logger3.warn("Payment verification: wrong sender", {
824
+ expected: expectedSender,
825
+ actual: tx.from
826
+ });
827
+ return false;
828
+ }
829
+ if (tx.to?.toLowerCase() !== this.wallet.address.toLowerCase()) {
830
+ logger3.warn("Payment verification: wrong recipient", {
831
+ expected: this.wallet.address,
832
+ actual: tx.to
833
+ });
834
+ return false;
835
+ }
836
+ if (tx.value < BigInt(expectedAmountWei)) {
837
+ logger3.warn("Payment verification: insufficient amount", {
838
+ expected: expectedAmountWei,
839
+ actual: tx.value.toString()
840
+ });
841
+ return false;
842
+ }
843
+ this.usedPayments.add(normalizedHash);
844
+ logger3.info("Payment verified successfully", {
845
+ txHash,
846
+ sender: tx.from,
847
+ amount: ethers.formatEther(tx.value)
848
+ });
849
+ return true;
850
+ } catch (error) {
851
+ logger3.error("Payment verification error", { txHash, error: error.message });
852
+ return false;
853
+ }
854
+ }
783
855
  };
784
856
 
785
857
  // src/a2a/WorldA2AClient.ts
@@ -791,13 +863,14 @@ var WorldA2AClient = class {
791
863
  * Add an agent to the client pool
792
864
  */
793
865
  addAgent(agentUrl) {
794
- if (this.clients.has(agentUrl)) {
795
- logger4.debug(`Agent already in client pool: ${agentUrl}`);
866
+ const baseUrl = this.extractBaseUrl(agentUrl);
867
+ if (this.clients.has(baseUrl)) {
868
+ logger4.debug(`Agent already in client pool: ${baseUrl}`);
796
869
  return;
797
870
  }
798
- const client = new A2AClient({ agentUrl });
799
- this.clients.set(agentUrl, client);
800
- logger4.info(`Added agent to A2A client pool: ${agentUrl}`);
871
+ const client = new A2AClient({ agentUrl: baseUrl });
872
+ this.clients.set(baseUrl, client);
873
+ logger4.info(`Added agent to A2A client pool: ${baseUrl}`);
801
874
  }
802
875
  /**
803
876
  * Remove an agent from the client pool
@@ -867,6 +940,17 @@ var WorldA2AClient = class {
867
940
  getAgentUrls() {
868
941
  return Array.from(this.clients.keys());
869
942
  }
943
+ /**
944
+ * Extract base URL (protocol://host:port) from a full URL
945
+ */
946
+ extractBaseUrl(url) {
947
+ try {
948
+ const parsed = new URL(url);
949
+ return `${parsed.protocol}//${parsed.host}`;
950
+ } catch {
951
+ return url;
952
+ }
953
+ }
870
954
  };
871
955
 
872
956
  // src/engine/WorldActions.ts
@@ -1234,6 +1318,18 @@ var TickOrchestrator = class {
1234
1318
 
1235
1319
  // src/a2a/MessageRouter.ts
1236
1320
  var logger8 = createLogger("MessageRouter");
1321
+ var c = {
1322
+ reset: "\x1B[0m",
1323
+ bold: "\x1B[1m",
1324
+ dim: "\x1B[2m",
1325
+ blue: "\x1B[34m",
1326
+ green: "\x1B[32m",
1327
+ yellow: "\x1B[33m",
1328
+ magenta: "\x1B[35m",
1329
+ cyan: "\x1B[36m",
1330
+ red: "\x1B[31m",
1331
+ gray: "\x1B[90m"
1332
+ };
1237
1333
  var MessageRouter = class {
1238
1334
  constructor(a2aClient, getAgents) {
1239
1335
  this.a2aClient = a2aClient;
@@ -1246,6 +1342,7 @@ var MessageRouter = class {
1246
1342
  const agents = this.getAgents();
1247
1343
  const sender = agents.find((a) => a.url === fromAgentUrl);
1248
1344
  if (!sender) {
1345
+ logger8.warn(`Message rejected \u2014 sender not in world: ${fromAgentUrl}`);
1249
1346
  return {
1250
1347
  success: false,
1251
1348
  error: `Sender not in world: ${fromAgentUrl}`
@@ -1253,11 +1350,19 @@ var MessageRouter = class {
1253
1350
  }
1254
1351
  const recipient = agents.find((a) => a.url === toAgentUrl);
1255
1352
  if (!recipient) {
1353
+ logger8.warn(`Message rejected \u2014 recipient not in world: ${toAgentUrl}`);
1256
1354
  return {
1257
1355
  success: false,
1258
1356
  error: `Recipient not in world: ${toAgentUrl}`
1259
1357
  };
1260
1358
  }
1359
+ console.log(
1360
+ `
1361
+ ${c.cyan}\u2501\u2501\u2501 MESSAGE \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${c.reset}
1362
+ ${c.bold}${c.blue}${sender.name}${c.reset} ${c.dim}\u2192${c.reset} ${c.bold}${c.green}${recipient.name}${c.reset}
1363
+ ${c.dim}\u2502${c.reset} ${this.truncate(message, 300)}
1364
+ ${c.cyan}\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${c.reset}`
1365
+ );
1261
1366
  const wrappedMessage = JSON.stringify({
1262
1367
  type: "world_message",
1263
1368
  from: {
@@ -1267,11 +1372,24 @@ var MessageRouter = class {
1267
1372
  },
1268
1373
  message
1269
1374
  });
1270
- logger8.info(`Routing message: ${sender.name} \u2192 ${recipient.name}`);
1271
- return this.a2aClient.sendToAgent(toAgentUrl, wrappedMessage, {
1375
+ const response = await this.a2aClient.sendToAgent(toAgentUrl, wrappedMessage, {
1272
1376
  type: "world_message",
1273
1377
  fromAgentUrl
1274
1378
  });
1379
+ if (response.success && response.reply) {
1380
+ console.log(
1381
+ `
1382
+ ${c.green}\u2501\u2501\u2501 REPLY \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${c.reset}
1383
+ ${c.bold}${c.green}${recipient.name}${c.reset} ${c.dim}\u2192${c.reset} ${c.bold}${c.blue}${sender.name}${c.reset}
1384
+ ${c.dim}\u2502${c.reset} ${this.truncate(response.reply, 300)}
1385
+ ${c.green}\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${c.reset}`
1386
+ );
1387
+ } else if (!response.success) {
1388
+ console.log(
1389
+ `${c.red} \u2717 ${recipient.name} failed to respond: ${response.error || "no response"}${c.reset}`
1390
+ );
1391
+ }
1392
+ return response;
1275
1393
  }
1276
1394
  /**
1277
1395
  * Broadcast a message from one agent to all other agents in the world
@@ -1280,6 +1398,7 @@ var MessageRouter = class {
1280
1398
  const agents = this.getAgents();
1281
1399
  const sender = agents.find((a) => a.url === fromAgentUrl);
1282
1400
  if (!sender) {
1401
+ logger8.warn(`Broadcast rejected \u2014 sender not in world: ${fromAgentUrl}`);
1283
1402
  const result = /* @__PURE__ */ new Map();
1284
1403
  result.set(fromAgentUrl, {
1285
1404
  success: false,
@@ -1287,6 +1406,14 @@ var MessageRouter = class {
1287
1406
  });
1288
1407
  return result;
1289
1408
  }
1409
+ const recipients = agents.filter((a) => a.url !== fromAgentUrl);
1410
+ console.log(
1411
+ `
1412
+ ${c.yellow}\u2501\u2501\u2501 BROADCAST \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${c.reset}
1413
+ ${c.bold}${c.blue}${sender.name}${c.reset} ${c.dim}\u2192${c.reset} ${c.bold}${c.yellow}ALL (${recipients.length} agents)${c.reset}
1414
+ ${c.dim}\u2502${c.reset} ${this.truncate(message, 300)}
1415
+ ${c.yellow}\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${c.reset}`
1416
+ );
1290
1417
  const wrappedMessage = JSON.stringify({
1291
1418
  type: "world_broadcast",
1292
1419
  from: {
@@ -1297,7 +1424,6 @@ var MessageRouter = class {
1297
1424
  message
1298
1425
  });
1299
1426
  const results = /* @__PURE__ */ new Map();
1300
- const recipients = agents.filter((a) => a.url !== fromAgentUrl);
1301
1427
  const promises = recipients.map(async (recipient) => {
1302
1428
  const response = await this.a2aClient.sendToAgent(
1303
1429
  recipient.url,
@@ -1305,11 +1431,32 @@ var MessageRouter = class {
1305
1431
  { type: "world_broadcast", fromAgentUrl }
1306
1432
  );
1307
1433
  results.set(recipient.url, response);
1434
+ if (response.success && response.reply) {
1435
+ console.log(
1436
+ `${c.green} \u21A9 ${c.bold}${recipient.name}${c.reset}${c.green} replied:${c.reset} ${this.truncate(response.reply, 200)}`
1437
+ );
1438
+ } else if (!response.success) {
1439
+ console.log(
1440
+ `${c.red} \u2717 ${recipient.name} failed: ${response.error || "no response"}${c.reset}`
1441
+ );
1442
+ }
1308
1443
  });
1309
1444
  await Promise.allSettled(promises);
1310
- logger8.info(`Broadcast from ${sender.name} to ${recipients.length} agents`);
1445
+ const succeeded = Array.from(results.values()).filter((r) => r.success).length;
1446
+ const failed = Array.from(results.values()).filter((r) => !r.success).length;
1447
+ console.log(
1448
+ `${c.dim} Broadcast complete: ${c.green}${succeeded} replied${c.reset}${c.dim}${failed > 0 ? `, ${c.red}${failed} failed${c.reset}` : ""}${c.reset}
1449
+ `
1450
+ );
1311
1451
  return results;
1312
1452
  }
1453
+ /**
1454
+ * Truncate long messages for logging
1455
+ */
1456
+ truncate(text, maxLen) {
1457
+ if (text.length <= maxLen) return text;
1458
+ return text.slice(0, maxLen) + "...";
1459
+ }
1313
1460
  };
1314
1461
 
1315
1462
  // src/engine/World.ts
@@ -1464,10 +1611,16 @@ var World = class {
1464
1611
  });
1465
1612
  logger9.info("World stopped");
1466
1613
  }
1614
+ /**
1615
+ * Get the blockchain client (for server endpoints)
1616
+ */
1617
+ getBlockchainClient() {
1618
+ return this.blockchainClient;
1619
+ }
1467
1620
  /**
1468
1621
  * Admit an agent to the world
1469
1622
  */
1470
- async admitAgent(card, walletAddress) {
1623
+ async admitAgent(card, walletAddress, paymentTxHash) {
1471
1624
  const decision = await this.evaluator.evaluate(card, this.agents.size, walletAddress);
1472
1625
  if (!decision.admitted) {
1473
1626
  throw new Error(`Agent admission denied: ${decision.reason}`);
@@ -1482,6 +1635,28 @@ var World = class {
1482
1635
  if (this.blockchainClient && this.config.blockchain?.requireMembership && walletAddress) {
1483
1636
  const hasMembership = await this.blockchainClient.hasMembership(walletAddress);
1484
1637
  if (!hasMembership) {
1638
+ const entryFeeWei = await this.blockchainClient.getEntryFeeWei();
1639
+ if (entryFeeWei !== "0" && BigInt(entryFeeWei) > 0n) {
1640
+ if (!paymentTxHash) {
1641
+ throw new Error(
1642
+ `Entry fee required: ${entryFeeWei} wei. Query GET /world/join-info for payment details, pay the fee, then include paymentTxHash in your join request.`
1643
+ );
1644
+ }
1645
+ const paymentValid = await this.blockchainClient.verifyPayment(
1646
+ paymentTxHash,
1647
+ walletAddress,
1648
+ entryFeeWei
1649
+ );
1650
+ if (!paymentValid) {
1651
+ throw new Error(
1652
+ `Payment verification failed for tx ${paymentTxHash}. Ensure the transaction is confirmed, sent from ${walletAddress}, to ${this.blockchainClient.getWalletAddress()}, for at least ${entryFeeWei} wei.`
1653
+ );
1654
+ }
1655
+ logger9.info(`Entry fee payment verified for ${card.name}`, {
1656
+ txHash: paymentTxHash,
1657
+ amount: entryFeeWei
1658
+ });
1659
+ }
1485
1660
  logger9.info(`Minting membership NFT for agent: ${card.name}`);
1486
1661
  await this.blockchainClient.mintMembership(walletAddress);
1487
1662
  } else {
@@ -1665,9 +1840,40 @@ function createWorldApp(world) {
1665
1840
  }
1666
1841
  });
1667
1842
  });
1843
+ app.get("/world/join-info", async (req, res) => {
1844
+ try {
1845
+ const blockchain = world.config.blockchain;
1846
+ const client = world.getBlockchainClient();
1847
+ if (!blockchain || !client) {
1848
+ return res.json({
1849
+ worldName: world.config.name,
1850
+ requiresPayment: false,
1851
+ entryFee: "0",
1852
+ paymentAddress: null,
1853
+ chainId: null,
1854
+ rpcUrl: null
1855
+ });
1856
+ }
1857
+ const entryFeeWei = await client.getEntryFeeWei();
1858
+ res.json({
1859
+ worldName: world.config.name,
1860
+ requiresPayment: entryFeeWei !== "0" && BigInt(entryFeeWei) > 0n,
1861
+ entryFee: entryFeeWei,
1862
+ paymentAddress: client.getWalletAddress(),
1863
+ chainId: blockchain.chainId || null,
1864
+ rpcUrl: blockchain.rpcUrl
1865
+ });
1866
+ } catch (error) {
1867
+ logger10.error("Failed to get join info:", error);
1868
+ res.status(500).json({
1869
+ success: false,
1870
+ error: error.message || "Failed to get join info"
1871
+ });
1872
+ }
1873
+ });
1668
1874
  app.post("/world/join", async (req, res) => {
1669
1875
  try {
1670
- const { agentUrl, walletAddress } = req.body;
1876
+ const { agentUrl, walletAddress, paymentTxHash } = req.body;
1671
1877
  if (!agentUrl) {
1672
1878
  return res.status(400).json({
1673
1879
  success: false,
@@ -1675,9 +1881,10 @@ function createWorldApp(world) {
1675
1881
  });
1676
1882
  }
1677
1883
  logger10.info(`Agent join request from: ${agentUrl}`, {
1678
- wallet: walletAddress || "none"
1884
+ wallet: walletAddress || "none",
1885
+ paymentTx: paymentTxHash ? `${paymentTxHash.slice(0, 10)}...` : "none"
1679
1886
  });
1680
- const cardFetcher = new (await import("./CardFetcher-3QKJ2I5P.js")).CardFetcher();
1887
+ const cardFetcher = new (await import("./CardFetcher-IMUPYNEQ.js")).CardFetcher();
1681
1888
  const result = await cardFetcher.fetchCard(agentUrl);
1682
1889
  if (!result.success || !result.card) {
1683
1890
  return res.status(400).json({
@@ -1685,7 +1892,7 @@ function createWorldApp(world) {
1685
1892
  error: `Failed to fetch agent card: ${result.error}`
1686
1893
  });
1687
1894
  }
1688
- await world.admitAgent(result.card, walletAddress);
1895
+ await world.admitAgent(result.card, walletAddress, paymentTxHash);
1689
1896
  res.json({
1690
1897
  success: true,
1691
1898
  message: "Agent admitted to world",
@@ -1843,6 +2050,7 @@ async function startWorldServer(world) {
1843
2050
  logger10.info("");
1844
2051
  logger10.info("Endpoints:");
1845
2052
  logger10.info(` GET / - World info`);
2053
+ logger10.info(` GET /world/join-info - Join requirements & entry fee`);
1846
2054
  logger10.info(` POST /world/join - Agent join request`);
1847
2055
  logger10.info(` GET /world/agents - List agents`);
1848
2056
  logger10.info(` GET /world/state - World state`);