cronos-agent-wallet 1.2.2 → 1.2.4
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 +40 -0
- package/dist/AgentClient.js +1 -6
- package/dist/config.d.ts +15 -0
- package/dist/config.js +10 -0
- package/dist/internal/AgentWallet.d.ts +16 -2
- package/dist/internal/AgentWallet.js +70 -1
- package/dist/internal/CronosUsdcExecutor.d.ts +3 -0
- package/dist/internal/CronosUsdcExecutor.js +6 -0
- package/dist/internal/PolicyRegistryAdapter.d.ts +32 -0
- package/dist/internal/PolicyRegistryAdapter.js +44 -0
- package/package.json +1 -1
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`.
|
|
@@ -64,9 +90,23 @@ async function main() {
|
|
|
64
90
|
| `chainId` | `number` | Yes | Chain ID (e.g., 338). Sent to backend for negotiation. |
|
|
65
91
|
| `usdcAddress` | `string` | Yes | ERC20 Token Address used for payment. |
|
|
66
92
|
| `dailyLimit` | `number` | No | Max USDC allowed to spend per 24h. Default: 1.0 |
|
|
93
|
+
| `strictPolicy` | `boolean` | No | If `true`, Agent crashes if local config hash != on-chain hash. Default: `true`. |
|
|
94
|
+
| `anchors` | `object` | No | On-chain registry addresses. Auto-filled for Cronos Testnet. |
|
|
95
|
+
| `analyticsUrl` | `string` | No | URL for centralized logging of payment decisions (e.g. `https://api.myapp.com/analytics`). |
|
|
67
96
|
| `allowedMerchants` | `string[]` | No | List of Merchant IDs to trust. If empty, allows all. |
|
|
68
97
|
| `trustedFacilitators` | `string[]` | No | List of Gateway URLs to trust (e.g., localhost). |
|
|
69
98
|
|
|
99
|
+
## 🛡️ Security Workflow (Strict Mode)
|
|
100
|
+
|
|
101
|
+
When `strictPolicy` is `true`, you must register your configuration hash on-chain whenever you change limits.
|
|
102
|
+
|
|
103
|
+
1. **Define Limits**: Set `dailyLimit` in your code.
|
|
104
|
+
2. **Seal Policy**: Run the setup script to write the hash to the `AgentPolicyRegistry`.
|
|
105
|
+
```bash
|
|
106
|
+
npx ts-node set_policy.ts
|
|
107
|
+
```
|
|
108
|
+
3. **Run Agent**: The Agent verifies `Local Hash == On-Chain Hash` before making any payment.
|
|
109
|
+
|
|
70
110
|
## API Reference
|
|
71
111
|
|
|
72
112
|
### `agent.fetch<T>(url, options): Promise<T>`
|
package/dist/AgentClient.js
CHANGED
|
@@ -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,15 @@ 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
|
+
strictPolicy: boolean;
|
|
27
|
+
};
|
package/dist/config.js
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
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
|
+
strictPolicy: true // 🛡️ ENFORCE: Agent will fail if policy hash mismatches chain
|
|
12
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { PaymentRequest, WalletContext
|
|
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
|
* ------------
|
|
@@ -12,6 +14,9 @@ import { PaymentExecutor } from "./executors";
|
|
|
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;
|
|
@@ -20,16 +25,25 @@ export declare class AgentWallet {
|
|
|
20
25
|
private trustedFacilitatorOrigins;
|
|
21
26
|
private stopped;
|
|
22
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
|
-
|
|
37
|
+
private policyRegistry?;
|
|
38
|
+
constructor(address: string, executor: PaymentExecutor, provider: ethers.Provider, wallet: ethers.Wallet, config: AgentConfig);
|
|
29
39
|
private getTodayDate;
|
|
30
40
|
init(): Promise<void>;
|
|
31
41
|
private loadState;
|
|
32
42
|
private saveState;
|
|
43
|
+
/**
|
|
44
|
+
* Checks if the local policy matches the on-chain anchor.
|
|
45
|
+
*/
|
|
46
|
+
private verifyPolicyAnchor;
|
|
33
47
|
getAddress(): string;
|
|
34
48
|
private getHeader;
|
|
35
49
|
private canonicalOrigin;
|
|
@@ -4,6 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AgentWallet = void 0;
|
|
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)));
|
|
16
|
+
}
|
|
7
17
|
const mongoose_1 = __importDefault(require("mongoose"));
|
|
8
18
|
const models_1 = require("./models");
|
|
9
19
|
/**
|
|
@@ -18,6 +28,9 @@ const models_1 = require("./models");
|
|
|
18
28
|
class AgentWallet {
|
|
19
29
|
address;
|
|
20
30
|
executor;
|
|
31
|
+
provider;
|
|
32
|
+
wallet;
|
|
33
|
+
config;
|
|
21
34
|
// ---------------- CONFIG ----------------
|
|
22
35
|
// Reduced limit for testing persistence (0.5 USDC)
|
|
23
36
|
dailyLimit = 0.5;
|
|
@@ -33,14 +46,23 @@ class AgentWallet {
|
|
|
33
46
|
]);
|
|
34
47
|
stopped = false;
|
|
35
48
|
isInitialized = false;
|
|
49
|
+
/**
|
|
50
|
+
* Replay protection
|
|
51
|
+
* key -> timestamp
|
|
52
|
+
*/
|
|
36
53
|
/**
|
|
37
54
|
* Replay protection
|
|
38
55
|
* key -> timestamp
|
|
39
56
|
*/
|
|
40
57
|
paidRequests = new Map();
|
|
41
|
-
|
|
58
|
+
// On-Chain Adapter
|
|
59
|
+
policyRegistry;
|
|
60
|
+
constructor(address, executor, provider, wallet, config) {
|
|
42
61
|
this.address = address;
|
|
43
62
|
this.executor = executor;
|
|
63
|
+
this.provider = provider;
|
|
64
|
+
this.wallet = wallet;
|
|
65
|
+
this.config = config;
|
|
44
66
|
if (config) {
|
|
45
67
|
if (config.dailyLimit !== undefined)
|
|
46
68
|
this.dailyLimit = config.dailyLimit;
|
|
@@ -50,6 +72,17 @@ class AgentWallet {
|
|
|
50
72
|
this.allowedMerchants = new Set(config.allowedMerchants);
|
|
51
73
|
if (config.trustedFacilitators)
|
|
52
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
|
+
}
|
|
53
86
|
}
|
|
54
87
|
this.lastResetDate = this.getTodayDate();
|
|
55
88
|
}
|
|
@@ -141,6 +174,42 @@ class AgentWallet {
|
|
|
141
174
|
console.error("[WALLET] Failed to save state to DB", error);
|
|
142
175
|
}
|
|
143
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
|
+
}
|
|
212
|
+
}
|
|
144
213
|
// ---------------- PUBLIC ----------------
|
|
145
214
|
getAddress() {
|
|
146
215
|
return this.address;
|
|
@@ -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
|
}
|
|
@@ -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;
|