cronos-agent-wallet 1.2.1 → 1.2.3

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
@@ -2,6 +2,32 @@
2
2
 
3
3
  The official SDK for building AI agents that can pay for resources on the Cronos blockchain using the x402 protocol.
4
4
 
5
+ ## Architecture
6
+
7
+ This SDK employs a **Hybrid "Exoskeleton" Architecture**:
8
+ - **Off-Chain**: Fast, free logic enforcement and state tracking (MongoDB).
9
+ - **On-Chain**: Immutable security anchors (Registry & Policy) to prevent tampering.
10
+
11
+ ```mermaid
12
+ graph TD
13
+ subgraph Agent SDK
14
+ Local[Local Policy Engine]
15
+ Anchor[On-Chain Policy Anchor (read-only)]
16
+ Executor[x402 Payment Executor]
17
+ end
18
+
19
+ subgraph Merchant Middleware
20
+ MRegistry[Merchant Registry (read-only)]
21
+ Verifier[x402 Verification]
22
+ end
23
+
24
+ Local -->|Checks Hash| Anchor
25
+ Local -->|Approves| Executor
26
+ Executor -->|Signed Claim| Verifier
27
+ Verifier -->|Verifies Identity| MRegistry
28
+ Verifier -->|Payment Tx| Cronos[Cronos EVM]
29
+ ```
30
+
5
31
  ## 🛡️ Security Features (v1.1.1)
6
32
  - **Zero Trust Payer:** Derives identity strictly from chain data.
7
33
  - **Strong Replay Protection:** Enforces cryptographic binding of `merchantId + route + nonce`.
@@ -28,12 +28,7 @@ class AgentClient {
28
28
  // 1. Create executor (chain-specific)
29
29
  const executor = new CronosUsdcExecutor_1.CronosUsdcExecutor(config.rpcUrl, config.privateKey, config.usdcAddress, config.chainId);
30
30
  // 2. Create wallet (policy + persistence)
31
- this.wallet = new AgentWallet_1.AgentWallet(executor.getAddress(), executor, {
32
- dailyLimit: config.dailyLimit,
33
- maxPerTransaction: config.maxPerTransaction,
34
- trustedFacilitators: config.trustedFacilitators,
35
- allowedMerchants: config.allowedMerchants,
36
- });
31
+ this.wallet = new AgentWallet_1.AgentWallet(executor.getAddress(), executor, executor.getProvider(), executor.getSigner(), config);
37
32
  // 3. Context passed per request
38
33
  this.context = {
39
34
  chainId: config.chainId,
package/dist/config.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ export interface OnChainAnchors {
2
+ merchantRegistry?: string;
3
+ agentPolicyRegistry?: string;
4
+ policyVerifier?: string;
5
+ }
1
6
  export interface AgentConfig {
2
7
  privateKey: string;
3
8
  rpcUrl: string;
@@ -8,5 +13,14 @@ export interface AgentConfig {
8
13
  allowedMerchants?: string[];
9
14
  trustedFacilitators?: string[];
10
15
  analyticsUrl?: string;
16
+ anchors?: OnChainAnchors;
17
+ strictPolicy?: boolean;
11
18
  merchantId?: string;
12
19
  }
20
+ export declare const AGENT_CONFIG_DEFAULTS: {
21
+ anchors: {
22
+ merchantRegistry: string;
23
+ agentPolicyRegistry: string;
24
+ policyVerifier: string;
25
+ };
26
+ };
package/dist/config.js CHANGED
@@ -1,2 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_CONFIG_DEFAULTS = void 0;
4
+ exports.AGENT_CONFIG_DEFAULTS = {
5
+ // Cronos Testnet Anchors
6
+ anchors: {
7
+ merchantRegistry: "0x1948175dDB81DA08a4cf17BE4E0C95B97dD11F5c",
8
+ agentPolicyRegistry: "0xce3b58c9ae8CA4724d6FA8684d2Cb89546FF4E43",
9
+ policyVerifier: "0xFCb2D2279256B62A1E4E07BCDed26B6546bBc33b"
10
+ }
11
+ };
@@ -1,5 +1,7 @@
1
- import { PaymentRequest, WalletContext, AgentWalletConfig } from "./types";
1
+ import { PaymentRequest, WalletContext } from "./types";
2
2
  import { PaymentExecutor } from "./executors";
3
+ import { AgentConfig } from "../config";
4
+ import { ethers } from "ethers";
3
5
  /**
4
6
  * AgentWallet
5
7
  * ------------
@@ -7,11 +9,14 @@ import { PaymentExecutor } from "./executors";
7
9
  * - Decides whether to pay
8
10
  * - Delegates execution to PaymentExecutor
9
11
  * - Enforces security + policy
10
- * - Persists state (Isomorphic: FS or LocalStorage)
12
+ * - Persists state to MongoDB
11
13
  */
12
14
  export declare class AgentWallet {
13
15
  private readonly address;
14
16
  private readonly executor;
17
+ private readonly provider;
18
+ private readonly wallet;
19
+ private readonly config;
15
20
  private dailyLimit;
16
21
  private maxPerTransaction;
17
22
  private spentToday;
@@ -19,16 +24,26 @@ export declare class AgentWallet {
19
24
  private allowedMerchants;
20
25
  private trustedFacilitatorOrigins;
21
26
  private stopped;
22
- private persistence;
27
+ private isInitialized;
28
+ /**
29
+ * Replay protection
30
+ * key -> timestamp
31
+ */
23
32
  /**
24
33
  * Replay protection
25
34
  * key -> timestamp
26
35
  */
27
36
  private paidRequests;
28
- constructor(address: string, executor: PaymentExecutor, config?: AgentWalletConfig);
37
+ private policyRegistry?;
38
+ constructor(address: string, executor: PaymentExecutor, provider: ethers.Provider, wallet: ethers.Wallet, config: AgentConfig);
29
39
  private getTodayDate;
40
+ init(): Promise<void>;
30
41
  private loadState;
31
42
  private saveState;
43
+ /**
44
+ * Checks if the local policy matches the on-chain anchor.
45
+ */
46
+ private verifyPolicyAnchor;
32
47
  getAddress(): string;
33
48
  private getHeader;
34
49
  private canonicalOrigin;
@@ -42,9 +57,9 @@ export declare class AgentWallet {
42
57
  emergencyStop(): void;
43
58
  shouldPay(request: PaymentRequest, context: WalletContext, originalChallenge?: {
44
59
  route?: string;
45
- }): {
60
+ }): Promise<{
46
61
  allow: boolean;
47
62
  reason?: string;
48
- };
63
+ }>;
49
64
  executePayment(request: PaymentRequest): Promise<string>;
50
65
  }
@@ -1,57 +1,21 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.AgentWallet = void 0;
4
- class NodePersistence {
5
- fs;
6
- path;
7
- statePath = "";
8
- constructor() {
9
- // Dynamic require to avoid bundling issues in browser
10
- try {
11
- this.fs = require("fs");
12
- this.path = require("path");
13
- this.statePath = this.path.resolve(__dirname, "wallet-state.json");
14
- }
15
- catch (e) { /* Browser environment */ }
16
- }
17
- load() {
18
- if (!this.fs || !this.fs.existsSync(this.statePath))
19
- return null;
20
- try {
21
- return JSON.parse(this.fs.readFileSync(this.statePath, "utf-8"));
22
- }
23
- catch (e) {
24
- console.warn("[WALLET] Failed to load local state file", e);
25
- return null;
26
- }
27
- }
28
- save(data) {
29
- if (!this.fs)
30
- return;
31
- try {
32
- const tmpPath = this.statePath + ".tmp";
33
- this.fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
34
- this.fs.renameSync(tmpPath, this.statePath);
35
- }
36
- catch (e) {
37
- console.error("[WALLET] Failed to save local state file", e);
38
- }
39
- }
40
- }
41
- class BrowserPersistence {
42
- key = "cronos_agent_wallet_state";
43
- load() {
44
- if (typeof localStorage === "undefined")
45
- return null;
46
- const item = localStorage.getItem(this.key);
47
- return item ? JSON.parse(item) : null;
48
- }
49
- save(data) {
50
- if (typeof localStorage === "undefined")
51
- return;
52
- localStorage.setItem(this.key, JSON.stringify(data));
53
- }
7
+ const PolicyRegistryAdapter_1 = require("./PolicyRegistryAdapter");
8
+ const ethers_1 = require("ethers");
9
+ // Helper to hash policy config
10
+ function hashPolicy(config) {
11
+ const policyData = {
12
+ dailyLimit: config.dailyLimit || 0,
13
+ maxPerTransaction: config.maxPerTransaction || 0
14
+ };
15
+ return ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(JSON.stringify(policyData)));
54
16
  }
17
+ const mongoose_1 = __importDefault(require("mongoose"));
18
+ const models_1 = require("./models");
55
19
  /**
56
20
  * AgentWallet
57
21
  * ------------
@@ -59,11 +23,14 @@ class BrowserPersistence {
59
23
  * - Decides whether to pay
60
24
  * - Delegates execution to PaymentExecutor
61
25
  * - Enforces security + policy
62
- * - Persists state (Isomorphic: FS or LocalStorage)
26
+ * - Persists state to MongoDB
63
27
  */
64
28
  class AgentWallet {
65
29
  address;
66
30
  executor;
31
+ provider;
32
+ wallet;
33
+ config;
67
34
  // ---------------- CONFIG ----------------
68
35
  // Reduced limit for testing persistence (0.5 USDC)
69
36
  dailyLimit = 0.5;
@@ -78,15 +45,24 @@ class AgentWallet {
78
45
  "https://cronos-x-402-production.up.railway.app",
79
46
  ]);
80
47
  stopped = false;
81
- persistence;
48
+ isInitialized = false;
49
+ /**
50
+ * Replay protection
51
+ * key -> timestamp
52
+ */
82
53
  /**
83
54
  * Replay protection
84
55
  * key -> timestamp
85
56
  */
86
57
  paidRequests = new Map();
87
- constructor(address, executor, config) {
58
+ // On-Chain Adapter
59
+ policyRegistry;
60
+ constructor(address, executor, provider, wallet, config) {
88
61
  this.address = address;
89
62
  this.executor = executor;
63
+ this.provider = provider;
64
+ this.wallet = wallet;
65
+ this.config = config;
90
66
  if (config) {
91
67
  if (config.dailyLimit !== undefined)
92
68
  this.dailyLimit = config.dailyLimit;
@@ -96,56 +72,143 @@ class AgentWallet {
96
72
  this.allowedMerchants = new Set(config.allowedMerchants);
97
73
  if (config.trustedFacilitators)
98
74
  this.trustedFacilitatorOrigins = new Set(config.trustedFacilitators);
75
+ // Initialize On-Chain Adapter if configured
76
+ if (config.anchors?.agentPolicyRegistry) {
77
+ this.policyRegistry = new PolicyRegistryAdapter_1.PolicyRegistryAdapter(config.anchors.agentPolicyRegistry, provider, wallet);
78
+ // Non-blocking check for setup/warning
79
+ this.verifyPolicyAnchor().catch(err => {
80
+ console.error("[AgentWallet] Policy Anchor Warning:", err.message);
81
+ if (config.strictPolicy) {
82
+ process.exit(1); // Strict mode: fail fast
83
+ }
84
+ });
85
+ }
99
86
  }
100
- // Initialize Persistence based on env
101
- if (typeof window === "undefined") {
102
- this.persistence = new NodePersistence();
103
- }
104
- else {
105
- this.persistence = new BrowserPersistence();
106
- }
107
- this.loadState();
87
+ this.lastResetDate = this.getTodayDate();
108
88
  }
109
89
  // ---------------- PERSISTENCE ----------------
110
90
  getTodayDate() {
111
91
  return new Date().toISOString().split("T")[0]; // YYYY-MM-DD
112
92
  }
113
- loadState() {
114
- const state = this.persistence.load();
115
- if (state) {
116
- // 1. Check if we need to reset for a new day
117
- const today = this.getTodayDate();
118
- if (state.lastResetDate !== today) {
119
- console.log(`[WALLET] New day detected! Resetting limit. (Last: ${state.lastResetDate}, Today: ${today})`);
120
- this.spentToday = 0;
121
- this.lastResetDate = today;
122
- // Cleanup old requests (older than 24h) to prevent memory leak
123
- const MAX_AGE_MS = 24 * 60 * 60 * 1000;
124
- const now = Date.now();
125
- this.paidRequests = new Map(state.paidRequests.filter(([_, ts]) => now - ts < MAX_AGE_MS));
93
+ async init() {
94
+ if (this.isInitialized)
95
+ return;
96
+ try {
97
+ // Auto-connect if not connected
98
+ if (mongoose_1.default.connection.readyState === 0) {
99
+ if (!process.env.MONGODB_URI) {
100
+ console.warn("[WALLET] MONGODB_URI missing. Persistence disabled.");
101
+ return;
102
+ }
103
+ await mongoose_1.default.connect(process.env.MONGODB_URI);
104
+ console.log("[WALLET] Connected to MongoDB");
105
+ }
106
+ await this.loadState();
107
+ this.isInitialized = true;
108
+ }
109
+ catch (error) {
110
+ console.error("[WALLET] Initialization failed:", error);
111
+ }
112
+ }
113
+ async loadState() {
114
+ try {
115
+ const doc = await models_1.AgentWalletModel.findOne({ address: this.address }).lean();
116
+ if (doc) {
117
+ // Helper to normalize DB Map vs POJO
118
+ const rawObj = doc.paidRequests || {};
119
+ const rawMap = rawObj instanceof Map
120
+ ? rawObj
121
+ : new Map(Object.entries(rawObj));
122
+ // 1. Check if we need to reset for a new day
123
+ const today = this.getTodayDate();
124
+ if (doc.lastResetDate !== today) {
125
+ console.log(`[WALLET] New day detected! Resetting limit. (Last: ${doc.lastResetDate}, Today: ${today})`);
126
+ this.spentToday = 0;
127
+ this.lastResetDate = today;
128
+ // Cleanup old requests (older than 24h)
129
+ const MAX_AGE_MS = 24 * 60 * 60 * 1000;
130
+ const now = Date.now();
131
+ this.paidRequests = new Map();
132
+ for (const [key, val] of rawMap.entries()) {
133
+ const ts = Number(val);
134
+ if (!isNaN(ts) && (now - ts < MAX_AGE_MS)) {
135
+ this.paidRequests.set(key, ts);
136
+ }
137
+ }
138
+ // Save cleanup immediately
139
+ await this.saveState();
140
+ }
141
+ else {
142
+ console.log(`[WALLET] State loaded. Spent today: ${doc.spentToday.toFixed(4)} / ${this.dailyLimit}`);
143
+ this.spentToday = doc.spentToday;
144
+ this.lastResetDate = doc.lastResetDate;
145
+ this.paidRequests = new Map();
146
+ for (const [key, val] of rawMap.entries()) {
147
+ const ts = Number(val);
148
+ if (!isNaN(ts)) {
149
+ this.paidRequests.set(key, ts);
150
+ }
151
+ }
152
+ }
126
153
  }
127
154
  else {
128
- console.log(`[WALLET] State loaded. Spent today: ${state.spentToday.toFixed(4)} / ${this.dailyLimit}`);
129
- this.spentToday = state.spentToday;
130
- this.lastResetDate = state.lastResetDate;
131
- // Also cleanup on every load to be safe
132
- const MAX_AGE_MS = 24 * 60 * 60 * 1000;
133
- const now = Date.now();
134
- this.paidRequests = new Map(state.paidRequests.filter(([_, ts]) => now - ts < MAX_AGE_MS));
155
+ // Initialize fresh
156
+ this.lastResetDate = this.getTodayDate();
157
+ await this.saveState();
135
158
  }
136
159
  }
137
- else {
138
- // Initialize fresh
139
- this.lastResetDate = this.getTodayDate();
160
+ catch (error) {
161
+ console.warn("[WALLET] Failed to load state from DB", error);
140
162
  }
141
163
  }
142
- saveState() {
143
- const state = {
144
- lastResetDate: this.lastResetDate,
145
- spentToday: this.spentToday,
146
- paidRequests: Array.from(this.paidRequests.entries())
147
- };
148
- this.persistence.save(state);
164
+ async saveState() {
165
+ try {
166
+ await models_1.AgentWalletModel.findOneAndUpdate({ address: this.address }, {
167
+ address: this.address,
168
+ lastResetDate: this.lastResetDate,
169
+ spentToday: this.spentToday,
170
+ paidRequests: this.paidRequests
171
+ }, { upsert: true, new: true });
172
+ }
173
+ catch (error) {
174
+ console.error("[WALLET] Failed to save state to DB", error);
175
+ }
176
+ }
177
+ /**
178
+ * Checks if the local policy matches the on-chain anchor.
179
+ */
180
+ async verifyPolicyAnchor() {
181
+ if (!this.policyRegistry)
182
+ return;
183
+ try {
184
+ console.log("[AgentWallet] Verifying policy against on-chain anchor...");
185
+ const onChain = await this.policyRegistry.getPolicy(this.address);
186
+ // Check 1: Freeze status
187
+ if (onChain.isFrozen) {
188
+ this.stopped = true;
189
+ throw new Error("Agent policy is FROZEN on-chain. Emergency stop activated.");
190
+ }
191
+ // Check 2: Hash mismatch
192
+ const localHash = hashPolicy(this.config);
193
+ // Note: We check if on-chain hash is set (non-zero) before enforcing
194
+ if (onChain.policyHash !== ethers_1.ethers.ZeroHash && onChain.policyHash !== localHash) {
195
+ const msg = `Policy Hash Mismatch! Local: ${localHash}, Chain: ${onChain.policyHash}`;
196
+ if (this.config?.strictPolicy) {
197
+ throw new Error(msg);
198
+ }
199
+ else {
200
+ console.warn(`[AgentWallet] WARN: ${msg}. Running in PERMISSIVE mode.`);
201
+ }
202
+ }
203
+ else {
204
+ console.log("[AgentWallet] ✅ Policy verified on-chain.");
205
+ }
206
+ }
207
+ catch (error) {
208
+ console.error("[AgentWallet] Anchor check failed:", error.message);
209
+ if (this.config?.strictPolicy)
210
+ throw error;
211
+ }
149
212
  }
150
213
  // ---------------- PUBLIC ----------------
151
214
  getAddress() {
@@ -246,7 +309,10 @@ class AgentWallet {
246
309
  this.stopped = true;
247
310
  console.warn("[WALLET] Emergency stop activated!");
248
311
  }
249
- shouldPay(request, context, originalChallenge) {
312
+ async shouldPay(request, context, originalChallenge) {
313
+ // Ensure initialized
314
+ if (!this.isInitialized)
315
+ await this.init();
250
316
  // -1. Emergency Stop
251
317
  if (this.stopped) {
252
318
  return { allow: false, reason: "Emergency stop active" };
@@ -256,8 +322,8 @@ class AgentWallet {
256
322
  if (originalChallenge?.route && request.route !== originalChallenge.route) {
257
323
  return { allow: false, reason: `Route mismatch: Challenge=${request.route}, Expected=${originalChallenge.route}` };
258
324
  }
259
- // 0. Currency check [NEW]
260
- if (request.currency !== "USDC") {
325
+ // 0. Currency check [UPDATED]
326
+ if (request.currency !== "USDC" && request.currency !== "CRO") {
261
327
  return { allow: false, reason: `Unsupported currency: ${request.currency}` };
262
328
  }
263
329
  // 1. Chain verification
@@ -287,6 +353,9 @@ class AgentWallet {
287
353
  }
288
354
  // ---------------- PAYMENT EXECUTION ----------------
289
355
  async executePayment(request) {
356
+ // Ensure initialized
357
+ if (!this.isInitialized)
358
+ await this.init();
290
359
  const key = this.paymentKey(request);
291
360
  if (this.paidRequests.has(key)) {
292
361
  throw new Error("Replay detected: payment already executed");
@@ -297,7 +366,7 @@ class AgentWallet {
297
366
  this.spentToday += request.amount;
298
367
  this.paidRequests.set(key, Date.now());
299
368
  // Persist immediately
300
- this.saveState();
369
+ await this.saveState();
301
370
  return proof;
302
371
  }
303
372
  }
@@ -1,4 +1,5 @@
1
1
  import "dotenv/config";
2
+ import { ethers } from "ethers";
2
3
  import { PaymentExecutor } from "./executors";
3
4
  import { PaymentRequest } from "./types";
4
5
  export declare class CronosUsdcExecutor implements PaymentExecutor {
@@ -11,4 +12,6 @@ export declare class CronosUsdcExecutor implements PaymentExecutor {
11
12
  private withRetry;
12
13
  execute(request: PaymentRequest): Promise<string>;
13
14
  getAddress(): string;
15
+ getProvider(): ethers.JsonRpcProvider;
16
+ getSigner(): ethers.Wallet;
14
17
  }
@@ -52,40 +52,55 @@ class CronosUsdcExecutor {
52
52
  throw lastError;
53
53
  }
54
54
  async execute(request) {
55
- const amount = ethers_1.ethers.parseUnits(request.amount.toString(), 6 // USDC decimals
56
- );
57
- console.log(`[CRONOS] Paying ${request.amount} USDC -> ${request.payTo}
58
- merchant = ${request.merchantId}
59
- route = ${request.route}
60
- nonce = ${request.nonce} `);
61
55
  // 0. Connection Check
62
56
  if (!this.chainId)
63
57
  await this.verifyChain(Number(request.chainId));
58
+ console.log(`[CRONOS] Paying ${request.amount} ${request.currency} -> ${request.payTo}
59
+ merchant = ${request.merchantId}
60
+ route = ${request.route}
61
+ nonce = ${request.nonce}`);
62
+ // --- PATH A: Native CRO Payment ---
63
+ if (request.currency === "CRO" || request.currency === "TCRO") {
64
+ const amount = ethers_1.ethers.parseEther(request.amount.toString());
65
+ // 1. Balance Check
66
+ const balance = await this.withRetry(() => this.provider.getBalance(this.wallet.address));
67
+ const MIN_GAS = ethers_1.ethers.parseEther("0.05"); // Reserve gas
68
+ if (balance < (amount + MIN_GAS)) {
69
+ throw new Error(`Insufficient CRO balance: have ${ethers_1.ethers.formatEther(balance)}, need ${request.amount} + gas`);
70
+ }
71
+ // 2. Send Tx
72
+ const txHandler = await this.withRetry(() => this.wallet.sendTransaction({
73
+ to: request.payTo,
74
+ value: amount
75
+ }));
76
+ console.log(`[CRONOS] CRO Tx sent: ${txHandler.hash}. Waiting for confirmation...`);
77
+ // 3. Wait
78
+ const receipt = await Promise.race([
79
+ this.withRetry(() => txHandler.wait(CONFIRMATIONS)),
80
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Transaction confirmation timeout")), TX_TIMEOUT_MS)),
81
+ ]);
82
+ if (!receipt || receipt.status !== 1)
83
+ throw new Error("CRO transfer failed");
84
+ console.log(`[CRONOS] Payment confirmed: ${receipt.hash}`);
85
+ return receipt.hash;
86
+ }
87
+ // --- PATH B: USDC Payment (Legacy) ---
88
+ const amount = ethers_1.ethers.parseUnits(request.amount.toString(), 6); // USDC decimals
64
89
  // 1. Balance check (Retryable)
65
- // Note: balanceOf returns bigint in v6
66
90
  const balance = await this.withRetry(() => this.usdc.balanceOf(this.wallet.address));
67
91
  if (balance < amount) {
68
- throw new Error(`Insufficient USDC balance: have ${ethers_1.ethers.formatUnits(balance, 6)}, need ${request.amount} `);
92
+ throw new Error(`Insufficient USDC balance: have ${ethers_1.ethers.formatUnits(balance, 6)}, need ${request.amount}`);
69
93
  }
70
- // ... inside class ...
71
94
  // 1.5 Gas Check (CRO Balance)
72
95
  const croBalance = await this.withRetry(() => this.provider.getBalance(this.wallet.address));
73
- const MIN_GAS = ethers_1.ethers.parseEther("0.1"); // Reserve 0.1 CRO for gas
96
+ const MIN_GAS = ethers_1.ethers.parseEther("0.1");
74
97
  if (croBalance < MIN_GAS) {
75
98
  throw new errors_1.AgentError(`Insufficient CRO for gas: have ${ethers_1.ethers.formatEther(croBalance)}, need > 0.1`, "INSUFFICIENT_FUNDS");
76
99
  }
77
- // 2. Gas Estimation (Safety)
78
- try {
79
- const gasEstimate = await this.withRetry(() => this.usdc.transfer.estimateGas(request.payTo, amount));
80
- // Optional: Cap gas limit if needed, usually estimate is fine
81
- }
82
- catch (e) {
83
- throw new Error(`Gas estimation failed(likely insufficient funds for gas): ${e.message} `);
84
- }
85
- // 3. Send tx
100
+ // 2. Send tx
86
101
  const tx = await this.withRetry(() => this.usdc.transfer(request.payTo, amount));
87
102
  console.log(`[CRONOS] Tx sent: ${tx.hash}. Waiting for confirmation...`);
88
- // 4. Wait with timeout + confirmations
103
+ // 3. Wait
89
104
  const receipt = await Promise.race([
90
105
  this.withRetry(() => tx.wait(CONFIRMATIONS)),
91
106
  new Promise((_, reject) => setTimeout(() => reject(new Error("Transaction confirmation timeout")), TX_TIMEOUT_MS)),
@@ -93,11 +108,17 @@ nonce = ${request.nonce} `);
93
108
  if (!receipt || receipt.status !== 1) {
94
109
  throw new Error("USDC transfer failed (Reverted)");
95
110
  }
96
- console.log(`[CRONOS] Payment confirmed: ${receipt.hash} `);
111
+ console.log(`[CRONOS] Payment confirmed: ${receipt.hash}`);
97
112
  return receipt.hash;
98
113
  }
99
114
  getAddress() {
100
115
  return this.wallet.address;
101
116
  }
117
+ getProvider() {
118
+ return this.provider;
119
+ }
120
+ getSigner() {
121
+ return this.wallet;
122
+ }
102
123
  }
103
124
  exports.CronosUsdcExecutor = CronosUsdcExecutor;
@@ -0,0 +1,32 @@
1
+ import { ethers, Provider, Wallet } from "ethers";
2
+ export interface OnChainPolicy {
3
+ dailySpendLimit: bigint;
4
+ maxPerTransaction: bigint;
5
+ policyHash: string;
6
+ isFrozen: boolean;
7
+ lastUpdated: bigint;
8
+ }
9
+ export interface PolicyInput {
10
+ dailySpendLimit: bigint;
11
+ maxPerTransaction: bigint;
12
+ policyHash: string;
13
+ }
14
+ export declare class PolicyRegistryAdapter {
15
+ private address;
16
+ private provider;
17
+ private signer?;
18
+ private contract;
19
+ constructor(address: string, provider: Provider, signer?: Wallet | undefined);
20
+ /**
21
+ * Reads the policy for the given agent from the blockchain.
22
+ * @param agentAddress The address to query
23
+ * @returns The policy struct or null if not found
24
+ */
25
+ getPolicy(agentAddress: string): Promise<OnChainPolicy>;
26
+ /**
27
+ * Writes the policy to the blockchain.
28
+ * @warning This is a gas-consuming transaction.
29
+ * @warning Should strictly be used during SETUP or RECOVERY, not during normal agent operation.
30
+ */
31
+ setPolicy(policy: PolicyInput): Promise<ethers.TransactionReceipt>;
32
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PolicyRegistryAdapter = void 0;
4
+ const ethers_1 = require("ethers");
5
+ const ABI = [
6
+ "function getPolicy(address agentAddress) external view returns (uint256 dailySpendLimit, uint256 maxPerTransaction, bytes32 policyHash, bool isFrozen, uint256 lastUpdated)",
7
+ "function setPolicy(uint256 dailySpendLimit, uint256 maxPerTransaction, bytes32 policyHash) external",
8
+ "event PolicySet(address indexed agent, uint256 dailySpendLimit, uint256 maxPerTransaction, bytes32 policyHash, uint256 timestamp)"
9
+ ];
10
+ class PolicyRegistryAdapter {
11
+ address;
12
+ provider;
13
+ signer;
14
+ contract;
15
+ constructor(address, provider, signer) {
16
+ this.address = address;
17
+ this.provider = provider;
18
+ this.signer = signer;
19
+ this.contract = new ethers_1.Contract(address, ABI, signer || provider);
20
+ }
21
+ /**
22
+ * Reads the policy for the given agent from the blockchain.
23
+ * @param agentAddress The address to query
24
+ * @returns The policy struct or null if not found
25
+ */
26
+ async getPolicy(agentAddress) {
27
+ return this.contract.getPolicy(agentAddress);
28
+ }
29
+ /**
30
+ * Writes the policy to the blockchain.
31
+ * @warning This is a gas-consuming transaction.
32
+ * @warning Should strictly be used during SETUP or RECOVERY, not during normal agent operation.
33
+ */
34
+ async setPolicy(policy) {
35
+ if (!this.signer) {
36
+ throw new Error("PolicyRegistryAdapter: No signer available for setPolicy");
37
+ }
38
+ // Runtime Guard: We add a parameter or check logic to prevent accidental usage.
39
+ // For now, explicit naming and documentation is the primary guard, plus the requirement of a signer.
40
+ const tx = await this.contract.setPolicy(policy.dailySpendLimit, policy.maxPerTransaction, policy.policyHash);
41
+ return tx.wait();
42
+ }
43
+ }
44
+ exports.PolicyRegistryAdapter = PolicyRegistryAdapter;
@@ -0,0 +1,15 @@
1
+ import { Document } from "mongoose";
2
+ interface IAgentWalletState extends Document {
3
+ address: string;
4
+ lastResetDate: string;
5
+ spentToday: number;
6
+ paidRequests: Map<string, number>;
7
+ }
8
+ export declare const AgentWalletModel: import("mongoose").Model<IAgentWalletState, {}, {}, {}, Document<unknown, {}, IAgentWalletState, {}, import("mongoose").DefaultSchemaOptions> & IAgentWalletState & Required<{
9
+ _id: import("mongoose").Types.ObjectId;
10
+ }> & {
11
+ __v: number;
12
+ } & {
13
+ id: string;
14
+ }, any, IAgentWalletState>;
15
+ export {};
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentWalletModel = void 0;
4
+ const mongoose_1 = require("mongoose");
5
+ const AgentWalletStateSchema = new mongoose_1.Schema({
6
+ address: { type: String, required: true, unique: true },
7
+ lastResetDate: { type: String, required: true },
8
+ spentToday: { type: Number, required: true, default: 0 },
9
+ paidRequests: {
10
+ type: Map,
11
+ of: Number,
12
+ default: new Map()
13
+ }
14
+ });
15
+ exports.AgentWalletModel = (0, mongoose_1.model)("AgentWalletState", AgentWalletStateSchema);
@@ -76,7 +76,7 @@ async function x402Request(url, wallet, context, options = {}) {
76
76
  throw new Error(`Failed to parse 402 challenge from Headers OR Body. Header error: ${headerErr}. Body error: ${bodyErr}`);
77
77
  }
78
78
  }
79
- const decision = wallet.shouldPay(paymentRequest, context, {
79
+ const decision = await wallet.shouldPay(paymentRequest, context, {
80
80
  route: wallet.parse402Header(res.headers).route // Validate against what the server claimed in headers
81
81
  });
82
82
  const agentAddress = wallet.getAddress();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronos-agent-wallet",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [