open-agents-ai 0.86.0 → 0.87.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 CHANGED
@@ -66,7 +66,7 @@ D8AgCTrxpDKD5meJ2bpAfVwcST3NF3EPuy9xczYycnXn
66
66
  - **OpenCode delegation** — offload coding tasks to opencode (sst/opencode) as an autonomous sub-agent with auto-install, progress monitoring, and result evaluation
67
67
  - **Long-horizon cron agents** — schedule recurring autonomous agent tasks with goals, completion criteria, execution history, and automatic evaluation (daily code reviews, weekly dep updates, continuous monitoring)
68
68
  - **Nexus P2P networking** — decentralized agent-to-agent communication via [open-agents-nexus](https://www.npmjs.com/package/open-agents-nexus). Join rooms, discover peers, share resources, and communicate across the agent mesh with encrypted P2P transport
69
- - **x402 micropayments** — agents can register inference services with USDC pricing on Base network, create secure wallets (AES-256-GCM encrypted, keys never exposed to LLM context), and participate in paid inference markets
69
+ - **x402 micropayments** — native x402 payment rails via open-agents-nexus@1.5.6. Agents create secp256k1/EVM wallets (AES-256-GCM encrypted, keys never exposed to LLM), register inference with USDC pricing on Base, auto-handle `payment_required`/`payment_proof` negotiation, track earnings/spending in ledger.jsonl, enforce budget policies, and sign gasless EIP-3009 transfers
70
70
  - **Inference capability proof** — benchmark local models with anti-spoofing SHA-256 hashed proofs, generate capability scorecards for peer verification
71
71
  - **Ralph Loop** — iterative task execution that keeps retrying until completion criteria are met
72
72
  - **Dream Mode** — creative idle exploration modeled after real sleep architecture (NREM→REM cycles)
@@ -926,7 +926,7 @@ The steering sub-agent uses the same model and backend as the main agent with `m
926
926
  - **LATS** (Zhou et al., 2024) — mid-execution replanning with user-provided value signals improves task completion on complex multi-step problems
927
927
  - **AutoGen** (Wu et al., 2023) — human-in-the-loop patterns work best when user messages are expanded into structured instructions, reducing ambiguity for the primary agent
928
928
 
929
- ## Tools (47)
929
+ ## Tools (54)
930
930
 
931
931
  | Tool | Description |
932
932
  |------|-------------|
@@ -994,6 +994,13 @@ The steering sub-agent uses the same model and backend as the main agent with `m
994
994
  | `aiwg_setup` | Deploy AIWG SDLC framework |
995
995
  | `aiwg_health` | Analyze project SDLC health and readiness |
996
996
  | `aiwg_workflow` | Execute AIWG commands and workflows |
997
+ | **Nexus P2P & x402 Payments** | |
998
+ | `nexus` | Decentralized agent networking — connect, rooms, DMs, peer discovery, invoke capabilities, metering, trust/blocking, IPFS storage |
999
+ | `nexus:expose` | Expose local Ollama models as metered inference capabilities with OpenRouter-based pricing |
1000
+ | `nexus:wallet_create` | Generate secp256k1/EVM wallet (Base mainnet USDC) with AES-256-GCM encryption + x402-wallet.key |
1001
+ | `nexus:spend` | Sign EIP-3009 USDC TransferWithAuthorization — budget-checked, gasless for payer |
1002
+ | `nexus:ledger_status` | Transaction history (earned/spent/pending USDC) |
1003
+ | `nexus:budget_set` | Configure spending limits — daily cap, per-invoke max, auto-approve threshold |
997
1004
 
998
1005
  Read-only tools execute concurrently when called in the same turn. Mutating tools run sequentially.
999
1006
 
@@ -1045,6 +1052,55 @@ All context-dependent values scale automatically with the actual context window
1045
1052
  | Tool output cap | 2K-8K chars (scales with context) |
1046
1053
  | File read limits | 80-120 line cap for small/medium context windows |
1047
1054
 
1055
+ ## x402 Payment Rails & Nexus P2P
1056
+
1057
+ Agents can earn and spend USDC on Base mainnet through the native x402 protocol built into [open-agents-nexus@1.5.6](https://www.npmjs.com/package/open-agents-nexus).
1058
+
1059
+ ### Wallet & Identity
1060
+ ```
1061
+ nexus(action='wallet_create') # Generate secp256k1/EVM wallet
1062
+ nexus(action='wallet_status') # Address, balance, ledger summary
1063
+ ```
1064
+ Creates `wallet.enc` (AES-256-GCM encrypted) and `x402-wallet.key` (plaintext, 0600 perms for daemon x402 module). Keys never enter LLM context.
1065
+
1066
+ ### Expose Inference with Pricing
1067
+ ```
1068
+ nexus(action='expose', margin='0.5') # 50% of OpenRouter market rate
1069
+ nexus(action='expose', margin='0') # Free (self-hosted)
1070
+ nexus(action='pricing_menu') # Current pricing for exposed models
1071
+ ```
1072
+ When margin > 0, capabilities are registered with USDC pricing metadata. The daemon auto-handles `invoke.payment_required` → `payment_proof` negotiation via x402.
1073
+
1074
+ ### Spend — Gasless USDC Transfers (EIP-3009)
1075
+ ```
1076
+ nexus(action='spend', target_address='0x...', amount_usdc='0.10')
1077
+ ```
1078
+ Signs an EIP-3009 `TransferWithAuthorization`. Budget-checked before signing. The recipient (or any facilitator) submits on-chain — no gas needed from the payer. Proof saved to `.oa/nexus/pending-transfer.json`.
1079
+
1080
+ ### Ledger & Budget
1081
+ ```
1082
+ nexus(action='ledger_status') # Earned/spent/pending history
1083
+ nexus(action='budget_status') # Limits and today's usage
1084
+ nexus(action='budget_set', daily_limit='1.00') # Max daily spend
1085
+ nexus(action='budget_set', per_invoke_max='0.10') # Max per invocation
1086
+ nexus(action='budget_set', auto_approve_below='0.01') # Auto-approve micropayments
1087
+ ```
1088
+
1089
+ ### How x402 Works (End to End)
1090
+ 1. **wallet_create** → generates wallet + x402-wallet.key for daemon signing
1091
+ 2. **expose** with margin > 0 → registers capabilities with USDC pricing
1092
+ 3. Peer calls **invoke_capability** → daemon sends `payment_required` with terms
1093
+ 4. Consumer's daemon auto-signs `payment_proof` → provider validates → invoke proceeds
1094
+ 5. Metering hook writes payment events to `ledger.jsonl`
1095
+ 6. **spend** → direct agent-to-agent USDC transfers (EIP-3009, gasless)
1096
+
1097
+ ### Security Model
1098
+ - Private keys: AES-256-GCM encrypted in `wallet.enc` (scrypt-derived key)
1099
+ - `x402-wallet.key`: plaintext (0600 perms) — used only by daemon subprocess
1100
+ - Budget policy: daily limits, per-invoke caps, circuit breaker, peer denylist
1101
+ - All outbound messages scanned for key material before sending
1102
+ - Keys NEVER appear in tool output, logs, or LLM context
1103
+
1048
1104
  ## Voice Feedback (TTS)
1049
1105
 
1050
1106
  ```bash
package/dist/index.js CHANGED
@@ -11244,6 +11244,8 @@ function dlog(msg) { try { appendFileSync(logFile, new Date().toISOString() + '
11244
11244
  const pidFile = join(nexusDir, 'daemon.pid');
11245
11245
  const invocationsDir = join(nexusDir, 'invocations');
11246
11246
  const meteringFile = join(nexusDir, 'metering.jsonl');
11247
+ const x402KeyPath = join(nexusDir, 'x402-wallet.key');
11248
+ const hasX402Key = existsSync(x402KeyPath);
11247
11249
 
11248
11250
  mkdirSync(inboxDir, { recursive: true });
11249
11251
  mkdirSync(invocationsDir, { recursive: true });
@@ -11252,7 +11254,7 @@ mkdirSync(invocationsDir, { recursive: true });
11252
11254
  writeFileSync(pidFile, String(process.pid));
11253
11255
 
11254
11256
  const keyPath = join(nexusDir, 'identity.key');
11255
- const nexus = new NexusClient({
11257
+ var nexusOpts = {
11256
11258
  keyStorePath: keyPath,
11257
11259
  agentName,
11258
11260
  agentType,
@@ -11264,7 +11266,18 @@ const nexus = new NexusClient({
11264
11266
  enableCircuitRelay: true,
11265
11267
  usePublicBootstrap: true,
11266
11268
  trustPolicy: { denylist: [], allowlist: [] },
11267
- });
11269
+ };
11270
+ if (hasX402Key) {
11271
+ nexusOpts.x402 = {
11272
+ enabled: true,
11273
+ walletKeyPath: x402KeyPath,
11274
+ maxPaymentPerRequest: '5000000',
11275
+ };
11276
+ if (process.env.ALCHEMY_API_KEY) {
11277
+ nexusOpts.x402.alchemyApiKey = process.env.ALCHEMY_API_KEY;
11278
+ }
11279
+ }
11280
+ const nexus = new NexusClient(nexusOpts);
11268
11281
 
11269
11282
  const rooms = new Map();
11270
11283
  let connected = false;
@@ -11643,6 +11656,18 @@ async function handleCmd(cmd) {
11643
11656
  // Register as nexus capability
11644
11657
  const capName = 'inference:' + model.name.replace(/[^a-zA-Z0-9._-]/g, '_');
11645
11658
  if (typeof nexus.registerCapability === 'function') {
11659
+ var capOpts = {};
11660
+ if (margin > 0 && entry.pricing.input_per_1m_tokens > 0) {
11661
+ var tokensPerReq = 1000;
11662
+ var amountPerReq = Math.round(entry.pricing.input_per_1m_tokens * tokensPerReq);
11663
+ if (amountPerReq > 0) {
11664
+ capOpts.pricing = {
11665
+ amount: String(amountPerReq),
11666
+ currency: 'USDC',
11667
+ description: 'Inference: ' + model.name,
11668
+ };
11669
+ }
11670
+ }
11646
11671
  nexus.registerCapability(capName, async (request, stream) => {
11647
11672
  const logEntry = {
11648
11673
  ts: Date.now(),
@@ -11719,7 +11744,7 @@ async function handleCmd(cmd) {
11719
11744
  });
11720
11745
  }
11721
11746
  stream.close();
11722
- });
11747
+ }, capOpts);
11723
11748
  }
11724
11749
  }
11725
11750
 
@@ -11822,6 +11847,37 @@ process.on('unhandledRejection', (reason) => {
11822
11847
  }
11823
11848
  } catch {}
11824
11849
 
11850
+ // Write payment events to ledger.jsonl
11851
+ try {
11852
+ if (nexus.metering && typeof nexus.metering.addHook === 'function') {
11853
+ nexus.metering.addHook(function(record) {
11854
+ if (!record.payment) return;
11855
+ var ledgerFile = join(nexusDir, 'ledger.jsonl');
11856
+ var entry = {
11857
+ timestamp: new Date().toISOString(),
11858
+ type: record.direction === 'inbound' ? 'earned' : 'spent',
11859
+ amount: String(record.payment.amount || 0),
11860
+ amountUsd: (Number(record.payment.amount || 0) / 1000000).toFixed(6),
11861
+ peer: record.peerId || 'unknown',
11862
+ capability: record.service || record.capability || 'unknown',
11863
+ txHash: record.payment.txHash || '',
11864
+ note: 'auto:metering',
11865
+ };
11866
+ try { appendFileSync(ledgerFile, JSON.stringify(entry) + '\\n'); } catch {}
11867
+ });
11868
+ }
11869
+ } catch {}
11870
+
11871
+ // Init x402 wallet if key file exists
11872
+ if (hasX402Key && nexus.x402 && typeof nexus.x402.initWallet === 'function') {
11873
+ try {
11874
+ var addr = nexus.x402.initWallet(x402KeyPath);
11875
+ dlog('x402 wallet initialized: ' + addr);
11876
+ } catch (e) {
11877
+ dlog('x402 wallet init failed: ' + (e.message || e));
11878
+ }
11879
+ }
11880
+
11825
11881
  // v1.5.0: Client-level events for global message/DM/invoke routing
11826
11882
  try {
11827
11883
  if (typeof nexus.on === 'function') {
@@ -11876,7 +11932,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
11876
11932
  ];
11877
11933
  NexusTool = class {
11878
11934
  name = "nexus";
11879
- description = "Decentralized agent-to-agent communication via open-agents-nexus v1.5.0. Spawns a background Node.js process with real network sockets for libp2p + NATS P2P mesh. Simple flow: connect \u2192 join room \u2192 send messages. Auto-installs/updates nexus if needed. EXPOSE: expose action queries Ollama models, fetches OpenRouter market rates, registers metered inference capabilities so peers can invoke your models. pricing_menu shows rates. v1.5.0: register_capability, block_peer/unblock_peer, metering_status, room_members. Also supports direct peer invoke, IPFS storage, DMs, discovery, x402, inference proofs.";
11935
+ description = "Decentralized agent-to-agent communication via open-agents-nexus v1.5.0. Spawns a background Node.js process with real network sockets for libp2p + NATS P2P mesh. Simple flow: connect \u2192 join room \u2192 send messages. Auto-installs/updates nexus if needed. EXPOSE: expose action queries Ollama models, fetches OpenRouter market rates, registers metered inference capabilities so peers can invoke your models. pricing_menu shows rates. v1.5.0: register_capability, block_peer/unblock_peer, metering_status, room_members. Also supports direct peer invoke, IPFS storage, DMs, discovery, x402, inference proofs. WALLET: wallet_create generates EVM wallet (secp256k1, Base mainnet USDC). wallet_status shows address, USDC balance, and ledger summary. PAYMENTS: ledger_status tracks earnings/spending. budget_status/budget_set configure spending limits. spend: sign EIP-3009 USDC transfer (target_address + amount_usdc). x402 native payment via daemon.";
11880
11936
  parameters = {
11881
11937
  type: "object",
11882
11938
  properties: {
@@ -11908,7 +11964,11 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
11908
11964
  "metering_status",
11909
11965
  "room_members",
11910
11966
  "expose",
11911
- "pricing_menu"
11967
+ "pricing_menu",
11968
+ "ledger_status",
11969
+ "budget_status",
11970
+ "budget_set",
11971
+ "spend"
11912
11972
  ],
11913
11973
  description: "The nexus action to perform"
11914
11974
  },
@@ -11963,6 +12023,26 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
11963
12023
  margin: {
11964
12024
  type: "string",
11965
12025
  description: "Price margin multiplier for expose (0.5 = 50% of market rate, 0 = free). Default: 0.5"
12026
+ },
12027
+ daily_limit: {
12028
+ type: "string",
12029
+ description: "For budget_set: max daily USDC spend (e.g. '1.00')"
12030
+ },
12031
+ per_invoke_max: {
12032
+ type: "string",
12033
+ description: "For budget_set: max per-invoke USDC spend (e.g. '0.10')"
12034
+ },
12035
+ auto_approve_below: {
12036
+ type: "string",
12037
+ description: "For budget_set: auto-approve payments below this USD amount (e.g. '0.01')"
12038
+ },
12039
+ target_address: {
12040
+ type: "string",
12041
+ description: "For spend: target EVM address (0x + 40 hex chars)"
12042
+ },
12043
+ amount_usdc: {
12044
+ type: "string",
12045
+ description: "For spend: USDC amount to transfer (e.g. '0.10')"
11966
12046
  }
11967
12047
  },
11968
12048
  required: ["action"],
@@ -12063,6 +12143,18 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
12063
12143
  case "pricing_menu":
12064
12144
  result = await this.sendDaemonCmd("pricing_menu", args);
12065
12145
  break;
12146
+ case "ledger_status":
12147
+ result = await this.doLedgerStatus();
12148
+ break;
12149
+ case "budget_status":
12150
+ result = await this.doBudgetStatus();
12151
+ break;
12152
+ case "budget_set":
12153
+ result = await this.doBudgetSet(args);
12154
+ break;
12155
+ case "spend":
12156
+ result = await this.doSpend(args);
12157
+ break;
12066
12158
  default:
12067
12159
  return { success: false, output: "", error: `Unknown nexus action: ${action}`, durationMs: Date.now() - start };
12068
12160
  }
@@ -12396,6 +12488,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
12396
12488
  }
12397
12489
  return lines.join("\n");
12398
12490
  }
12491
+ // ---------------------------------------------------------------------------
12492
+ // Step 1: Wallet v2 — secp256k1 via viem for proper EVM addresses
12493
+ // ---------------------------------------------------------------------------
12399
12494
  async doWalletStatus() {
12400
12495
  await this.ensureDir();
12401
12496
  const walletPath = join28(this.nexusDir, "wallet.enc");
@@ -12404,13 +12499,33 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
12404
12499
  }
12405
12500
  try {
12406
12501
  const w = JSON.parse(await readFile13(walletPath, "utf8"));
12407
- return [
12502
+ const lines = [
12408
12503
  `Wallet Status:`,
12409
12504
  ` Address: ${w.address}`,
12505
+ ` Version: ${w.version} (${w.version === 2 ? "secp256k1/EVM" : "legacy SHA256"})`,
12506
+ ` Network: ${w.network || "none"} (chain ${w.chainId || "N/A"})`,
12410
12507
  ` Created: ${w.createdAt}`,
12411
12508
  ` Encryption: AES-256-GCM + scrypt`,
12412
12509
  ` NOTE: Private key is encrypted and NEVER accessible to the agent.`
12413
- ].join("\n");
12510
+ ];
12511
+ if (w.version === 2 && w.address) {
12512
+ const balance = await this.queryUsdcBalance(w.address, w.chainId || 8453);
12513
+ if (balance !== null) {
12514
+ const usdcHuman = (Number(balance) / 1e6).toFixed(6);
12515
+ lines.push(` USDC Balance: $${usdcHuman}`);
12516
+ const budget = await this.loadBudget();
12517
+ if (budget && Number(balance) < budget.circuitBreakerUsdc) {
12518
+ lines.push(` \u26A0 CIRCUIT BREAKER: Balance below $${(budget.circuitBreakerUsdc / 1e6).toFixed(2)} \u2014 all payments BLOCKED`);
12519
+ } else if (budget && Number(balance) < budget.lowBalanceAlertUsdc) {
12520
+ lines.push(` \u26A0 LOW BALANCE: Below $${(budget.lowBalanceAlertUsdc / 1e6).toFixed(2)} threshold`);
12521
+ }
12522
+ }
12523
+ }
12524
+ const ledgerSummary = await this.getLedgerSummary();
12525
+ if (ledgerSummary) {
12526
+ lines.push(` Earned: $${ledgerSummary.totalEarned} Spent: $${ledgerSummary.totalSpent} Net: $${ledgerSummary.net}`);
12527
+ }
12528
+ return lines.join("\n");
12414
12529
  } catch {
12415
12530
  return "Wallet file exists but could not be read.";
12416
12531
  }
@@ -12419,51 +12534,444 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
12419
12534
  await this.ensureDir();
12420
12535
  const walletPath = join28(this.nexusDir, "wallet.enc");
12421
12536
  if (existsSync21(walletPath))
12422
- return "Wallet already exists.";
12537
+ return "Wallet already exists. Delete wallet.enc to create a new one.";
12423
12538
  const userAddress = args.wallet_address;
12424
12539
  if (userAddress) {
12425
12540
  if (!/^0x[0-9a-fA-F]{40}$/.test(userAddress)) {
12426
12541
  return "Invalid address format. Expected 0x + 40 hex chars.";
12427
12542
  }
12428
12543
  const w2 = {
12429
- version: 1,
12544
+ version: 2,
12430
12545
  address: userAddress,
12431
12546
  encryptedKey: "",
12432
12547
  iv: "",
12433
12548
  authTag: "",
12434
12549
  salt: "",
12435
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
12550
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12551
+ network: "base",
12552
+ chainId: 8453
12436
12553
  };
12437
12554
  await writeFile12(walletPath, JSON.stringify(w2, null, 2));
12438
12555
  await chmod(walletPath, 384);
12439
- return `Wallet configured: ${userAddress} (user-managed)`;
12556
+ await this.ensureDefaultBudget();
12557
+ return `Wallet configured: ${userAddress} (user-managed, Base mainnet, no x402 key \u2014 spend/sign requires self-hosted wallet)`;
12558
+ }
12559
+ let address;
12560
+ let privKeyHex;
12561
+ try {
12562
+ const { privateKeyToAccount } = await import("viem/accounts");
12563
+ const privKey = randomBytes6(32);
12564
+ privKeyHex = privKey.toString("hex");
12565
+ const account = privateKeyToAccount(`0x${privKeyHex}`);
12566
+ address = account.address;
12567
+ privKey.fill(0);
12568
+ } catch {
12569
+ const privKey = randomBytes6(32);
12570
+ privKeyHex = privKey.toString("hex");
12571
+ address = "0x" + createHash("sha256").update(privKey).digest("hex").slice(0, 40);
12572
+ privKey.fill(0);
12440
12573
  }
12441
- const privKey = randomBytes6(32);
12442
- const address = "0x" + createHash("sha256").update(privKey).digest("hex").slice(0, 40);
12443
12574
  const salt = randomBytes6(32);
12444
12575
  const key = scryptSync(`${hostname()}:${userInfo().username}:nexus-wallet`, salt, 32, { N: 16384, r: 8, p: 1 });
12445
12576
  const iv = randomBytes6(16);
12446
12577
  const cipher = createCipheriv("aes-256-gcm", key, iv);
12447
- let enc = cipher.update(privKey.toString("hex"), "utf8", "hex");
12578
+ let enc = cipher.update(privKeyHex, "utf8", "hex");
12448
12579
  enc += cipher.final("hex");
12449
- privKey.fill(0);
12580
+ const x402KeyPath = join28(this.nexusDir, "x402-wallet.key");
12581
+ await writeFile12(x402KeyPath, "0x" + privKeyHex);
12582
+ await chmod(x402KeyPath, 384);
12583
+ privKeyHex = "0".repeat(64);
12450
12584
  const w = {
12451
- version: 1,
12585
+ version: 2,
12452
12586
  address,
12453
12587
  encryptedKey: enc,
12454
12588
  iv: iv.toString("hex"),
12455
12589
  authTag: cipher.getAuthTag().toString("hex"),
12456
12590
  salt: salt.toString("hex"),
12457
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
12591
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12592
+ network: "base",
12593
+ chainId: 8453
12458
12594
  };
12459
12595
  await writeFile12(walletPath, JSON.stringify(w, null, 2));
12460
12596
  await chmod(walletPath, 384);
12597
+ await this.ensureDefaultBudget();
12461
12598
  return [
12462
- `Wallet created: ${address}`,
12599
+ `Wallet created (v2, secp256k1/EVM):`,
12600
+ ` Address: ${address}`,
12601
+ ` Network: Base mainnet (chain 8453)`,
12602
+ ` Token: USDC`,
12463
12603
  ` Encrypted with AES-256-GCM (key NEVER visible to agent)`,
12464
- ` File: ${walletPath} (0600)`
12604
+ ` File: ${walletPath} (0600)`,
12605
+ ``,
12606
+ `Fund this address with USDC on Base to enable paid inference.`,
12607
+ `Free inference (margin=0) works without funding.`
12465
12608
  ].join("\n");
12466
12609
  }
12610
+ // ---------------------------------------------------------------------------
12611
+ // Step 2: USDC Balance Query via Base RPC
12612
+ // ---------------------------------------------------------------------------
12613
+ balanceCache = null;
12614
+ async queryUsdcBalance(address, chainId) {
12615
+ if (this.balanceCache && Date.now() - this.balanceCache.fetchedAt < 6e4) {
12616
+ return this.balanceCache.value;
12617
+ }
12618
+ const rpcUrl = chainId === 84532 ? "https://sepolia.base.org" : "https://mainnet.base.org";
12619
+ const usdcContract = chainId === 84532 ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
12620
+ const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
12621
+ const data = `0x70a08231${paddedAddress}`;
12622
+ try {
12623
+ const resp = await fetch(rpcUrl, {
12624
+ method: "POST",
12625
+ headers: { "Content-Type": "application/json" },
12626
+ body: JSON.stringify({
12627
+ jsonrpc: "2.0",
12628
+ id: 1,
12629
+ method: "eth_call",
12630
+ params: [{ to: usdcContract, data }, "latest"]
12631
+ }),
12632
+ signal: AbortSignal.timeout(5e3)
12633
+ });
12634
+ const json = await resp.json();
12635
+ if (json.result) {
12636
+ const balance = BigInt(json.result).toString();
12637
+ this.balanceCache = { value: balance, fetchedAt: Date.now() };
12638
+ return balance;
12639
+ }
12640
+ } catch {
12641
+ }
12642
+ return null;
12643
+ }
12644
+ // ---------------------------------------------------------------------------
12645
+ // Step 3: Ledger — Track earnings and expenditures
12646
+ // ---------------------------------------------------------------------------
12647
+ async doLedgerStatus() {
12648
+ await this.ensureDir();
12649
+ const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
12650
+ if (!existsSync21(ledgerPath)) {
12651
+ return "No ledger entries. Earnings and spending will be tracked here after x402 transactions.";
12652
+ }
12653
+ try {
12654
+ const raw = await readFile13(ledgerPath, "utf8");
12655
+ const entries = raw.trim().split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
12656
+ if (entries.length === 0) {
12657
+ return "Ledger is empty. No transactions recorded.";
12658
+ }
12659
+ let totalEarned = 0n, totalSpent = 0n, pending = 0n;
12660
+ for (const e of entries) {
12661
+ const amt = BigInt(e.amount);
12662
+ if (e.type === "earned")
12663
+ totalEarned += amt;
12664
+ else if (e.type === "spent")
12665
+ totalSpent += amt;
12666
+ else if (e.type === "pending")
12667
+ pending += amt;
12668
+ }
12669
+ const lines = [
12670
+ `Ledger Status (${entries.length} entries):`,
12671
+ ` Total Earned: $${(Number(totalEarned) / 1e6).toFixed(6)} USDC`,
12672
+ ` Total Spent: $${(Number(totalSpent) / 1e6).toFixed(6)} USDC`,
12673
+ ` Net: $${(Number(totalEarned - totalSpent) / 1e6).toFixed(6)} USDC`
12674
+ ];
12675
+ if (pending > 0n) {
12676
+ lines.push(` Pending: $${(Number(pending) / 1e6).toFixed(6)} USDC`);
12677
+ }
12678
+ const recent = entries.slice(-5);
12679
+ lines.push(``, `Recent transactions:`);
12680
+ for (const e of recent) {
12681
+ const sign = e.type === "earned" ? "+" : e.type === "spent" ? "-" : "~";
12682
+ const peerShort = (e.peer || "?").slice(0, 16) + "...";
12683
+ lines.push(` ${e.timestamp.slice(0, 19)} ${sign}$${e.amountUsd} ${e.capability} (${peerShort})`);
12684
+ }
12685
+ return lines.join("\n");
12686
+ } catch {
12687
+ return "Error reading ledger file.";
12688
+ }
12689
+ }
12690
+ async getLedgerSummary() {
12691
+ const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
12692
+ if (!existsSync21(ledgerPath))
12693
+ return null;
12694
+ try {
12695
+ const raw = await readFile13(ledgerPath, "utf8");
12696
+ const entries = raw.trim().split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
12697
+ let totalEarned = 0n, totalSpent = 0n;
12698
+ for (const e of entries) {
12699
+ const amt = BigInt(e.amount);
12700
+ if (e.type === "earned")
12701
+ totalEarned += amt;
12702
+ else if (e.type === "spent")
12703
+ totalSpent += amt;
12704
+ }
12705
+ return {
12706
+ totalEarned: (Number(totalEarned) / 1e6).toFixed(6),
12707
+ totalSpent: (Number(totalSpent) / 1e6).toFixed(6),
12708
+ net: (Number(totalEarned - totalSpent) / 1e6).toFixed(6)
12709
+ };
12710
+ } catch {
12711
+ return null;
12712
+ }
12713
+ }
12714
+ async appendLedger(entry) {
12715
+ await this.ensureDir();
12716
+ const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
12717
+ const line = JSON.stringify(entry) + "\n";
12718
+ await writeFile12(ledgerPath, existsSync21(ledgerPath) ? await readFile13(ledgerPath, "utf8") + line : line);
12719
+ }
12720
+ // ---------------------------------------------------------------------------
12721
+ // Step 4: Budget Policy — Spending limits and auto-approve thresholds
12722
+ // ---------------------------------------------------------------------------
12723
+ async loadBudget() {
12724
+ const budgetPath = join28(this.nexusDir, "budget.json");
12725
+ if (!existsSync21(budgetPath))
12726
+ return null;
12727
+ try {
12728
+ return JSON.parse(await readFile13(budgetPath, "utf8"));
12729
+ } catch {
12730
+ return null;
12731
+ }
12732
+ }
12733
+ async ensureDefaultBudget() {
12734
+ const budgetPath = join28(this.nexusDir, "budget.json");
12735
+ if (existsSync21(budgetPath))
12736
+ return;
12737
+ const defaultBudget = {
12738
+ version: 1,
12739
+ autoApproveBelow: 1e3,
12740
+ // $0.001 — auto-approve micropayments
12741
+ dailyLimitUsdc: 1e6,
12742
+ // $1.00/day default
12743
+ perInvokeMaxUsdc: 1e5,
12744
+ // $0.10 per invoke max
12745
+ requireConfirmationAbove: 5e5,
12746
+ // $0.50 — ask human above this
12747
+ lowBalanceAlertUsdc: 5e5,
12748
+ // $0.50 — warn when low
12749
+ circuitBreakerUsdc: 1e5,
12750
+ // $0.10 — refuse payments below this balance
12751
+ allowedCapabilities: ["inference:*", "transfer:direct"],
12752
+ deniedPeers: []
12753
+ };
12754
+ await this.ensureDir();
12755
+ await writeFile12(budgetPath, JSON.stringify(defaultBudget, null, 2));
12756
+ }
12757
+ async doBudgetStatus() {
12758
+ await this.ensureDir();
12759
+ const budget = await this.loadBudget();
12760
+ if (!budget) {
12761
+ return "No budget policy configured. Use wallet_create to initialize defaults, or budget_set to configure.";
12762
+ }
12763
+ const todaySpent = await this.getTodaySpending();
12764
+ const lines = [
12765
+ `Budget Policy:`,
12766
+ ` Auto-approve below: $${(budget.autoApproveBelow / 1e6).toFixed(6)}`,
12767
+ ` Per-invoke max: $${(budget.perInvokeMaxUsdc / 1e6).toFixed(6)}`,
12768
+ ` Daily limit: $${(budget.dailyLimitUsdc / 1e6).toFixed(6)}`,
12769
+ ` Today's spending: $${(todaySpent / 1e6).toFixed(6)} / $${(budget.dailyLimitUsdc / 1e6).toFixed(6)}`,
12770
+ ` Confirm above: $${(budget.requireConfirmationAbove / 1e6).toFixed(6)}`,
12771
+ ` Low balance alert: $${(budget.lowBalanceAlertUsdc / 1e6).toFixed(6)}`,
12772
+ ` Circuit breaker: $${(budget.circuitBreakerUsdc / 1e6).toFixed(6)}`,
12773
+ ` Allowed caps: ${budget.allowedCapabilities.join(", ")}`
12774
+ ];
12775
+ if (budget.deniedPeers.length > 0) {
12776
+ lines.push(` Denied peers: ${budget.deniedPeers.length} blocked`);
12777
+ }
12778
+ return lines.join("\n");
12779
+ }
12780
+ async doBudgetSet(args) {
12781
+ await this.ensureDir();
12782
+ let budget = await this.loadBudget();
12783
+ if (!budget) {
12784
+ await this.ensureDefaultBudget();
12785
+ budget = await this.loadBudget();
12786
+ }
12787
+ const parseUsdc = (s) => Math.round(parseFloat(s) * 1e6);
12788
+ const changes = [];
12789
+ if (args.daily_limit) {
12790
+ budget.dailyLimitUsdc = parseUsdc(args.daily_limit);
12791
+ changes.push(`daily_limit=$${args.daily_limit}`);
12792
+ }
12793
+ if (args.per_invoke_max) {
12794
+ budget.perInvokeMaxUsdc = parseUsdc(args.per_invoke_max);
12795
+ changes.push(`per_invoke_max=$${args.per_invoke_max}`);
12796
+ }
12797
+ if (args.auto_approve_below) {
12798
+ budget.autoApproveBelow = parseUsdc(args.auto_approve_below);
12799
+ changes.push(`auto_approve_below=$${args.auto_approve_below}`);
12800
+ }
12801
+ if (changes.length === 0) {
12802
+ return "No budget parameters provided. Use daily_limit, per_invoke_max, or auto_approve_below.";
12803
+ }
12804
+ const budgetPath = join28(this.nexusDir, "budget.json");
12805
+ await writeFile12(budgetPath, JSON.stringify(budget, null, 2));
12806
+ return `Budget updated: ${changes.join(", ")}`;
12807
+ }
12808
+ async getTodaySpending() {
12809
+ const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
12810
+ if (!existsSync21(ledgerPath))
12811
+ return 0;
12812
+ try {
12813
+ const todayPrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
12814
+ const raw = await readFile13(ledgerPath, "utf8");
12815
+ let total = 0;
12816
+ for (const line of raw.trim().split("\n")) {
12817
+ if (!line.trim())
12818
+ continue;
12819
+ const entry = JSON.parse(line);
12820
+ if (entry.type === "spent" && entry.timestamp.startsWith(todayPrefix)) {
12821
+ total += Number(BigInt(entry.amount));
12822
+ }
12823
+ }
12824
+ return total;
12825
+ } catch {
12826
+ return 0;
12827
+ }
12828
+ }
12829
+ async checkBudget(amountUsdc, capability, peerId) {
12830
+ const budget = await this.loadBudget();
12831
+ if (!budget)
12832
+ return { allowed: true };
12833
+ if (budget.deniedPeers.includes(peerId)) {
12834
+ return { allowed: false, reason: `Peer ${peerId.slice(0, 16)}... is denied by budget policy` };
12835
+ }
12836
+ const capAllowed = budget.allowedCapabilities.some((pattern) => {
12837
+ if (pattern === "*")
12838
+ return true;
12839
+ if (pattern.endsWith(":*"))
12840
+ return capability.startsWith(pattern.slice(0, -1));
12841
+ return capability === pattern;
12842
+ });
12843
+ if (!capAllowed) {
12844
+ return { allowed: false, reason: `Capability "${capability}" not in allowed list` };
12845
+ }
12846
+ if (amountUsdc > budget.perInvokeMaxUsdc) {
12847
+ return { allowed: false, reason: `Amount $${(amountUsdc / 1e6).toFixed(6)} exceeds per-invoke max $${(budget.perInvokeMaxUsdc / 1e6).toFixed(6)}` };
12848
+ }
12849
+ const todaySpent = await this.getTodaySpending();
12850
+ if (todaySpent + amountUsdc > budget.dailyLimitUsdc) {
12851
+ return { allowed: false, reason: `Would exceed daily limit ($${((todaySpent + amountUsdc) / 1e6).toFixed(6)} > $${(budget.dailyLimitUsdc / 1e6).toFixed(6)})` };
12852
+ }
12853
+ const walletPath = join28(this.nexusDir, "wallet.enc");
12854
+ if (existsSync21(walletPath)) {
12855
+ try {
12856
+ const w = JSON.parse(await readFile13(walletPath, "utf8"));
12857
+ if (w.version === 2 && w.address) {
12858
+ const balance = await this.queryUsdcBalance(w.address, w.chainId || 8453);
12859
+ if (balance !== null && Number(balance) < budget.circuitBreakerUsdc) {
12860
+ return { allowed: false, reason: `Balance $${(Number(balance) / 1e6).toFixed(6)} below circuit breaker threshold $${(budget.circuitBreakerUsdc / 1e6).toFixed(6)}` };
12861
+ }
12862
+ }
12863
+ } catch {
12864
+ }
12865
+ }
12866
+ return { allowed: true };
12867
+ }
12868
+ // ---------------------------------------------------------------------------
12869
+ // Step 5: Spend — Agent-initiated USDC transfer (EIP-3009)
12870
+ // ---------------------------------------------------------------------------
12871
+ async doSpend(args) {
12872
+ const targetAddress = args.target_address;
12873
+ const amountUsd = args.amount_usdc;
12874
+ if (!targetAddress || !/^0x[0-9a-fA-F]{40}$/.test(targetAddress))
12875
+ throw new Error("Valid target_address (0x + 40 hex) is required");
12876
+ if (!amountUsd || isNaN(parseFloat(amountUsd)))
12877
+ throw new Error("amount_usdc is required (e.g. '0.10')");
12878
+ const amountSmallest = Math.round(parseFloat(amountUsd) * 1e6);
12879
+ if (amountSmallest <= 0)
12880
+ throw new Error("amount must be positive");
12881
+ const budgetResult = await this.checkBudget(amountSmallest, "transfer:direct", "");
12882
+ if (!budgetResult.allowed)
12883
+ return `BLOCKED by budget policy: ${budgetResult.reason}`;
12884
+ const walletPath = join28(this.nexusDir, "wallet.enc");
12885
+ if (!existsSync21(walletPath))
12886
+ throw new Error("No wallet. Use wallet_create first.");
12887
+ const w = JSON.parse(await readFile13(walletPath, "utf8"));
12888
+ if (!w.encryptedKey)
12889
+ throw new Error("User-managed wallet cannot spend (no private key)");
12890
+ const salt = Buffer.from(w.salt, "hex");
12891
+ const key = scryptSync(`${hostname()}:${userInfo().username}:nexus-wallet`, salt, 32, { N: 16384, r: 8, p: 1 });
12892
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(w.iv, "hex"));
12893
+ decipher.setAuthTag(Buffer.from(w.authTag, "hex"));
12894
+ let privKeyHex = decipher.update(w.encryptedKey, "hex", "utf8");
12895
+ privKeyHex += decipher.final("utf8");
12896
+ try {
12897
+ const { privateKeyToAccount } = await import("viem/accounts");
12898
+ const account = privateKeyToAccount(`0x${privKeyHex}`);
12899
+ const nonce = "0x" + randomBytes6(32).toString("hex");
12900
+ const now = Math.floor(Date.now() / 1e3);
12901
+ const validAfter = now - 60;
12902
+ const validBefore = now + 3600;
12903
+ const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
12904
+ const domain = {
12905
+ name: "USD Coin",
12906
+ version: "2",
12907
+ chainId: 8453,
12908
+ verifyingContract: USDC_ADDRESS
12909
+ };
12910
+ const types = {
12911
+ TransferWithAuthorization: [
12912
+ { name: "from", type: "address" },
12913
+ { name: "to", type: "address" },
12914
+ { name: "value", type: "uint256" },
12915
+ { name: "validAfter", type: "uint256" },
12916
+ { name: "validBefore", type: "uint256" },
12917
+ { name: "nonce", type: "bytes32" }
12918
+ ]
12919
+ };
12920
+ const message = {
12921
+ from: account.address,
12922
+ to: targetAddress,
12923
+ value: BigInt(amountSmallest),
12924
+ validAfter: BigInt(validAfter),
12925
+ validBefore: BigInt(validBefore),
12926
+ nonce
12927
+ };
12928
+ const { signTypedData } = await import("viem/accounts");
12929
+ const signature = await signTypedData({
12930
+ privateKey: `0x${privKeyHex}`,
12931
+ domain,
12932
+ types,
12933
+ primaryType: "TransferWithAuthorization",
12934
+ message
12935
+ });
12936
+ await this.appendLedger({
12937
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12938
+ type: "spent",
12939
+ amount: String(amountSmallest),
12940
+ amountUsd: (amountSmallest / 1e6).toFixed(6),
12941
+ peer: targetAddress,
12942
+ capability: "transfer:direct",
12943
+ note: "signed, awaiting submission"
12944
+ });
12945
+ const proofFile = join28(this.nexusDir, "pending-transfer.json");
12946
+ const proof = {
12947
+ from: account.address,
12948
+ to: targetAddress,
12949
+ value: String(amountSmallest),
12950
+ validAfter: String(validAfter),
12951
+ validBefore: String(validBefore),
12952
+ nonce,
12953
+ signature,
12954
+ chainId: 8453,
12955
+ usdcContract: USDC_ADDRESS,
12956
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
12957
+ };
12958
+ await writeFile12(proofFile, JSON.stringify(proof, null, 2));
12959
+ return [
12960
+ `Transfer signed (EIP-3009 TransferWithAuthorization):`,
12961
+ ` From: ${account.address}`,
12962
+ ` To: ${targetAddress}`,
12963
+ ` Amount: $${(amountSmallest / 1e6).toFixed(6)} USDC`,
12964
+ ` Valid: ${new Date(validAfter * 1e3).toISOString()} to ${new Date(validBefore * 1e3).toISOString()}`,
12965
+ ` Signature: ${signature.slice(0, 20)}...`,
12966
+ ` Proof saved: ${proofFile}`,
12967
+ ``,
12968
+ `To submit on-chain: anyone can call USDC.transferWithAuthorization() with this proof.`,
12969
+ `The recipient or a facilitator can submit it \u2014 no gas needed from the payer.`
12970
+ ].join("\n");
12971
+ } finally {
12972
+ privKeyHex = "0".repeat(64);
12973
+ }
12974
+ }
12467
12975
  async doInferenceProof() {
12468
12976
  try {
12469
12977
  const psRaw = execSync21("ollama ps 2>/dev/null", { timeout: 1e4, encoding: "utf8" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.86.0",
3
+ "version": "0.87.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,11 @@
49
49
  "peer-to-peer",
50
50
  "inference-network",
51
51
  "secret-vault",
52
- "websocket"
52
+ "websocket",
53
+ "x402",
54
+ "usdc",
55
+ "eip-3009",
56
+ "micropayments"
53
57
  ],
54
58
  "author": "robit-man",
55
59
  "license": "MIT",
@@ -70,6 +74,7 @@
70
74
  },
71
75
  "optionalDependencies": {
72
76
  "moondream": "^0.2.0",
73
- "open-agents-nexus": "^1.5.6"
77
+ "open-agents-nexus": "^1.5.6",
78
+ "viem": "^2.47.4"
74
79
  }
75
80
  }
@@ -185,7 +185,7 @@ If you notice you're performing the SAME multi-step sequence for the 3rd time or
185
185
  - Test the tool mentally before creating — ensure the steps would work in order
186
186
  - Prefer 'project' scope unless the pattern genuinely applies to all projects
187
187
 
188
- ## Nexus P2P Networking (v1.5.0) — Decentralized Agent Communication
188
+ ## Nexus P2P Networking (v1.5.6) — Decentralized Agent Communication + x402 Payments
189
189
 
190
190
  You HAVE the nexus tool. It is one of your registered tools. USE IT when asked about connecting, messaging, or networking with other agents.
191
191
 
@@ -263,13 +263,46 @@ expose queries local Ollama for models, fetches live market rates from OpenRoute
263
263
  nexus capability (inference:{model_name}), and writes pricing to .oa/nexus/pricing.json.
264
264
  Peers can invoke your models via invoke_capability and see metered usage.
265
265
 
266
+ ### x402 Payment Rails (native, wired to open-agents-nexus@1.5.6)
267
+
268
+ wallet_create generates a secp256k1/EVM wallet on Base mainnet. An `x402-wallet.key` file
269
+ is auto-created alongside `wallet.enc` for the daemon's x402 module. When margin > 0 in
270
+ expose, registerCapability passes pricing metadata — the daemon auto-handles
271
+ `invoke.payment_required` → `payment_proof` negotiation.
272
+
273
+ nexus(action='wallet_create') — generate new EVM wallet (secp256k1, Base, USDC)
274
+ nexus(action='wallet_create', wallet_address='0x...') — register existing address (no x402 signing)
275
+ nexus(action='wallet_status') — address, USDC balance, ledger summary
276
+
277
+ ### Ledger & Budget
278
+ nexus(action='ledger_status') — transaction history (earned/spent/pending)
279
+ nexus(action='budget_status') — spending limits and today's usage
280
+ nexus(action='budget_set', daily_limit='1.00') — set daily USDC limit
281
+ nexus(action='budget_set', per_invoke_max='0.10') — max per invocation
282
+ nexus(action='budget_set', auto_approve_below='0.01') — auto-approve micropayments
283
+
284
+ ### Spend — Agent-Initiated USDC Transfer (EIP-3009)
285
+ nexus(action='spend', target_address='0x...', amount_usdc='0.10')
286
+
287
+ Signs an EIP-3009 TransferWithAuthorization for USDC on Base. Budget-checked before signing.
288
+ The signed proof is saved to `.oa/nexus/pending-transfer.json` — anyone can submit it on-chain
289
+ via `USDC.transferWithAuthorization()`. No gas needed from the payer.
290
+
291
+ ### x402 Flow Summary
292
+ 1. wallet_create → generates wallet + x402-wallet.key (plaintext, 0600, for daemon)
293
+ 2. expose with margin > 0 → registers capabilities with USDC pricing
294
+ 3. Peers invoke_capability → daemon auto-handles payment_required/payment_proof
295
+ 4. Metering hook writes payment events to ledger.jsonl
296
+ 5. spend → sign direct USDC transfers (EIP-3009)
297
+
266
298
  SECURITY: Wallet private keys are AES-256-GCM encrypted and NEVER accessible to you.
267
- All outbound messages are scanned for key material leaks.
299
+ x402-wallet.key is 0600-permissioned for daemon use only. All outbound messages are scanned
300
+ for key material leaks.
268
301
 
269
302
  When the user asks about expanding capabilities or connecting with other agents, suggest
270
303
  enabling nexus networking. Use expose to share your models with the network, pricing_menu
271
- to check rates, register_capability to serve custom invocations, and room_members to
272
- discover who's online.
304
+ to check rates, register_capability to serve custom invocations, room_members to
305
+ discover who's online, and spend for direct USDC transfers.
273
306
 
274
307
  ## Temporal Agency — Scheduling, Reminders & Long-Horizon Tasks
275
308