@agirails/sdk 2.5.3 → 2.5.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/dist/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +67 -22
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +12 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +30 -4
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/StandardAdapter.d.ts +20 -3
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +45 -11
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/cli/commands/publish.js +16 -4
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.js +16 -4
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/tx.js +31 -3
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/config/networks.d.ts +2 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +27 -22
- package/dist/config/networks.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +2 -1
- package/dist/level0/request.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +11 -5
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -1
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +27 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +52 -18
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +116 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +212 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +19 -0
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +100 -5
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/package.json +3 -6
- package/src/ACTPClient.ts +0 -1579
- package/src/abi/ACTPKernel.json +0 -1356
- package/src/abi/AgentRegistry.json +0 -915
- package/src/abi/ERC20.json +0 -40
- package/src/abi/EscrowVault.json +0 -134
- package/src/abi/IdentityRegistry.json +0 -316
- package/src/adapters/AdapterRegistry.ts +0 -173
- package/src/adapters/AdapterRouter.ts +0 -416
- package/src/adapters/BaseAdapter.ts +0 -498
- package/src/adapters/BasicAdapter.ts +0 -514
- package/src/adapters/IAdapter.ts +0 -292
- package/src/adapters/StandardAdapter.ts +0 -555
- package/src/adapters/X402Adapter.ts +0 -731
- package/src/adapters/index.ts +0 -60
- package/src/builders/DeliveryProofBuilder.ts +0 -327
- package/src/builders/QuoteBuilder.ts +0 -483
- package/src/builders/index.ts +0 -17
- package/src/cli/commands/balance.ts +0 -110
- package/src/cli/commands/batch.ts +0 -487
- package/src/cli/commands/config.ts +0 -231
- package/src/cli/commands/deploy-check.ts +0 -364
- package/src/cli/commands/deploy-env.ts +0 -120
- package/src/cli/commands/diff.ts +0 -141
- package/src/cli/commands/init.ts +0 -469
- package/src/cli/commands/mint.ts +0 -116
- package/src/cli/commands/pay.ts +0 -113
- package/src/cli/commands/publish.ts +0 -475
- package/src/cli/commands/pull.ts +0 -124
- package/src/cli/commands/register.ts +0 -247
- package/src/cli/commands/simulate.ts +0 -345
- package/src/cli/commands/time.ts +0 -302
- package/src/cli/commands/tx.ts +0 -448
- package/src/cli/commands/watch.ts +0 -211
- package/src/cli/index.ts +0 -134
- package/src/cli/utils/client.ts +0 -252
- package/src/cli/utils/config.ts +0 -389
- package/src/cli/utils/output.ts +0 -465
- package/src/cli/utils/wallet.ts +0 -109
- package/src/config/agirailsmd.ts +0 -262
- package/src/config/networks.ts +0 -275
- package/src/config/pendingPublish.ts +0 -237
- package/src/config/publishPipeline.ts +0 -359
- package/src/config/syncOperations.ts +0 -279
- package/src/erc8004/ERC8004Bridge.ts +0 -462
- package/src/erc8004/ReputationReporter.ts +0 -468
- package/src/erc8004/index.ts +0 -61
- package/src/errors/index.ts +0 -427
- package/src/index.ts +0 -364
- package/src/level0/Provider.ts +0 -117
- package/src/level0/ServiceDirectory.ts +0 -131
- package/src/level0/index.ts +0 -10
- package/src/level0/provide.ts +0 -132
- package/src/level0/request.ts +0 -432
- package/src/level1/Agent.ts +0 -1426
- package/src/level1/index.ts +0 -10
- package/src/level1/pricing/PriceCalculator.ts +0 -255
- package/src/level1/pricing/PricingStrategy.ts +0 -198
- package/src/level1/types/Job.ts +0 -179
- package/src/level1/types/Options.ts +0 -291
- package/src/level1/types/index.ts +0 -8
- package/src/protocol/ACTPKernel.ts +0 -808
- package/src/protocol/AgentRegistry.ts +0 -559
- package/src/protocol/DIDManager.ts +0 -629
- package/src/protocol/DIDResolver.ts +0 -554
- package/src/protocol/EASHelper.ts +0 -378
- package/src/protocol/EscrowVault.ts +0 -255
- package/src/protocol/EventMonitor.ts +0 -204
- package/src/protocol/MessageSigner.ts +0 -510
- package/src/protocol/ProofGenerator.ts +0 -339
- package/src/protocol/QuoteBuilder.ts +0 -15
- package/src/registry/AgentRegistryClient.ts +0 -202
- package/src/runtime/BlockchainRuntime.ts +0 -1015
- package/src/runtime/IACTPRuntime.ts +0 -306
- package/src/runtime/MockRuntime.ts +0 -1298
- package/src/runtime/MockStateManager.ts +0 -577
- package/src/runtime/index.ts +0 -25
- package/src/runtime/types/MockState.ts +0 -237
- package/src/storage/ArchiveBundleBuilder.ts +0 -561
- package/src/storage/ArweaveClient.ts +0 -946
- package/src/storage/FilebaseClient.ts +0 -790
- package/src/storage/index.ts +0 -96
- package/src/storage/types.ts +0 -348
- package/src/types/adapter.ts +0 -310
- package/src/types/agent.ts +0 -79
- package/src/types/did.ts +0 -223
- package/src/types/eip712.ts +0 -175
- package/src/types/erc8004.ts +0 -293
- package/src/types/escrow.ts +0 -27
- package/src/types/index.ts +0 -17
- package/src/types/message.ts +0 -145
- package/src/types/state.ts +0 -87
- package/src/types/transaction.ts +0 -69
- package/src/types/x402.ts +0 -251
- package/src/utils/ErrorRecoveryGuide.ts +0 -676
- package/src/utils/Helpers.ts +0 -688
- package/src/utils/IPFSClient.ts +0 -368
- package/src/utils/Logger.ts +0 -484
- package/src/utils/NonceManager.ts +0 -591
- package/src/utils/RateLimiter.ts +0 -534
- package/src/utils/ReceivedNonceTracker.ts +0 -567
- package/src/utils/SDKLifecycle.ts +0 -416
- package/src/utils/SecureNonce.ts +0 -78
- package/src/utils/Semaphore.ts +0 -276
- package/src/utils/UsedAttestationTracker.ts +0 -385
- package/src/utils/canonicalJson.ts +0 -38
- package/src/utils/circuitBreaker.ts +0 -324
- package/src/utils/computeTypeHash.ts +0 -48
- package/src/utils/fsSafe.ts +0 -80
- package/src/utils/index.ts +0 -80
- package/src/utils/retry.ts +0 -364
- package/src/utils/security.ts +0 -418
- package/src/utils/validation.ts +0 -540
- package/src/wallet/AutoWalletProvider.ts +0 -299
- package/src/wallet/EOAWalletProvider.ts +0 -69
- package/src/wallet/IWalletProvider.ts +0 -135
- package/src/wallet/aa/BundlerClient.ts +0 -274
- package/src/wallet/aa/DualNonceManager.ts +0 -173
- package/src/wallet/aa/PaymasterClient.ts +0 -174
- package/src/wallet/aa/TransactionBatcher.ts +0 -353
- package/src/wallet/aa/UserOpBuilder.ts +0 -246
- package/src/wallet/aa/constants.ts +0 -60
- package/src/wallet/keystore.ts +0 -240
package/src/ACTPClient.ts
DELETED
|
@@ -1,1579 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ACTPClient - Main entry point for AGIRAILS SDK
|
|
3
|
-
*
|
|
4
|
-
* Provides the unified API for interacting with the ACTP protocol
|
|
5
|
-
* through three different abstraction levels:
|
|
6
|
-
* - `basic`: High-level, opinionated API for simple use cases
|
|
7
|
-
* - `standard`: Balanced API with more control
|
|
8
|
-
* - `advanced`: Direct protocol access for full control
|
|
9
|
-
*
|
|
10
|
-
* @module ACTPClient
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* // Create client (auto-detects wallet from .actp/keystore.json or env vars)
|
|
15
|
-
* const client = await ACTPClient.create({
|
|
16
|
-
* mode: 'mock',
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* // Basic API - simplest approach
|
|
20
|
-
* const result = await client.basic.pay({
|
|
21
|
-
* to: '0xProvider...',
|
|
22
|
-
* amount: '100',
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* // Standard API - more control
|
|
26
|
-
* const txId = await client.standard.createTransaction({
|
|
27
|
-
* provider: '0xProvider...',
|
|
28
|
-
* amount: '100',
|
|
29
|
-
* });
|
|
30
|
-
* await client.standard.linkEscrow(txId);
|
|
31
|
-
*
|
|
32
|
-
* // Advanced API - direct protocol access
|
|
33
|
-
* const tx = await client.advanced.getTransaction(txId);
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
import * as path from 'path';
|
|
38
|
-
import * as os from 'os';
|
|
39
|
-
import * as fs from 'fs';
|
|
40
|
-
import { ethers } from 'ethers';
|
|
41
|
-
import { MockRuntime } from './runtime/MockRuntime';
|
|
42
|
-
import { MockStateManager } from './runtime/MockStateManager';
|
|
43
|
-
import { BlockchainRuntime } from './runtime/BlockchainRuntime';
|
|
44
|
-
import { IACTPRuntime, IMockRuntime } from './runtime/IACTPRuntime';
|
|
45
|
-
import { BasicAdapter } from './adapters/BasicAdapter';
|
|
46
|
-
import { StandardAdapter } from './adapters/StandardAdapter';
|
|
47
|
-
import { AdapterRegistry } from './adapters/AdapterRegistry';
|
|
48
|
-
import { AdapterRouter } from './adapters/AdapterRouter';
|
|
49
|
-
import { IAdapter, TransactionStatus } from './adapters/IAdapter';
|
|
50
|
-
import { UnifiedPayParams, UnifiedPayResult } from './types/adapter';
|
|
51
|
-
import { EASHelper, EASConfig } from './protocol/EASHelper';
|
|
52
|
-
import { ERC8004Bridge } from './erc8004/ERC8004Bridge';
|
|
53
|
-
import { ReputationReporter } from './erc8004/ReputationReporter';
|
|
54
|
-
import { ERC8004Network } from './types/erc8004';
|
|
55
|
-
import { getNetwork } from './config/networks';
|
|
56
|
-
import { IWalletProvider } from './wallet/IWalletProvider';
|
|
57
|
-
import { EOAWalletProvider } from './wallet/EOAWalletProvider';
|
|
58
|
-
import { AutoWalletProvider } from './wallet/AutoWalletProvider';
|
|
59
|
-
import { SmartWalletCall } from './wallet/aa/constants';
|
|
60
|
-
import { buildActivationBatch, ActivationScenario } from './wallet/aa/TransactionBatcher';
|
|
61
|
-
import { loadPendingPublish, deletePendingPublish, PendingPublish } from './config/pendingPublish';
|
|
62
|
-
import { sdkLogger } from './utils/Logger';
|
|
63
|
-
|
|
64
|
-
// ============================================================================
|
|
65
|
-
// Security: Path Validation
|
|
66
|
-
// ============================================================================
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Validates that a state directory path is safe to use.
|
|
70
|
-
*
|
|
71
|
-
* SECURITY: Prevents path traversal attacks by ensuring:
|
|
72
|
-
* 1. No '..' components in the path
|
|
73
|
-
* 2. No symbolic links that could escape the intended directory
|
|
74
|
-
* 3. Path resolves to a location within home directory or current working directory
|
|
75
|
-
*
|
|
76
|
-
* @param stateDirectory - The directory path to validate
|
|
77
|
-
* @throws Error if path is unsafe
|
|
78
|
-
*/
|
|
79
|
-
/** On-chain agent state from AgentRegistry. */
|
|
80
|
-
export interface OnChainAgentState {
|
|
81
|
-
registeredAt: bigint;
|
|
82
|
-
configHash: string;
|
|
83
|
-
listed: boolean;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const ZERO_HASH = '0x' + '0'.repeat(64);
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Read the on-chain agent state from AgentRegistry.
|
|
90
|
-
* Returns registeredAt, configHash, and listed fields.
|
|
91
|
-
*/
|
|
92
|
-
export async function getOnChainAgentState(
|
|
93
|
-
provider: ethers.JsonRpcProvider,
|
|
94
|
-
registryAddress: string,
|
|
95
|
-
agentAddress: string
|
|
96
|
-
): Promise<OnChainAgentState> {
|
|
97
|
-
const contract = new ethers.Contract(
|
|
98
|
-
registryAddress,
|
|
99
|
-
[
|
|
100
|
-
'function getAgent(address agentAddress) view returns ' +
|
|
101
|
-
'(tuple(address agentAddress, string did, string endpoint, bytes32[] serviceTypes, ' +
|
|
102
|
-
'uint256 stakedAmount, uint256 reputationScore, uint256 totalTransactions, ' +
|
|
103
|
-
'uint256 disputedTransactions, uint256 totalVolumeUSDC, uint256 registeredAt, ' +
|
|
104
|
-
'uint256 updatedAt, bool isActive, bytes32 configHash, string configCID, bool listed))',
|
|
105
|
-
],
|
|
106
|
-
provider
|
|
107
|
-
);
|
|
108
|
-
const profile = await contract.getAgent(agentAddress);
|
|
109
|
-
return {
|
|
110
|
-
registeredAt: profile.registeredAt,
|
|
111
|
-
configHash: profile.configHash,
|
|
112
|
-
listed: profile.listed,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Detect the lazy publish activation scenario.
|
|
118
|
-
*
|
|
119
|
-
* Decision matrix:
|
|
120
|
-
* - A: Not registered + has pending → first-time activation
|
|
121
|
-
* - B1: Registered + pending hash != on-chain hash + not listed → re-publish + list
|
|
122
|
-
* - B2: Registered + pending hash != on-chain hash + already listed → re-publish only
|
|
123
|
-
* - C: Pending hash == on-chain hash → stale pending, delete it
|
|
124
|
-
* - none: No pending publish file
|
|
125
|
-
*/
|
|
126
|
-
export function detectLazyPublishScenario(
|
|
127
|
-
onChainState: OnChainAgentState,
|
|
128
|
-
pendingPublish: PendingPublish | null
|
|
129
|
-
): ActivationScenario {
|
|
130
|
-
if (!pendingPublish) return 'none';
|
|
131
|
-
|
|
132
|
-
const isRegistered = onChainState.registeredAt > 0n;
|
|
133
|
-
const pendingHash = pendingPublish.configHash;
|
|
134
|
-
const onChainHash = onChainState.configHash;
|
|
135
|
-
|
|
136
|
-
if (!isRegistered) {
|
|
137
|
-
// Not registered — scenario A: full activation
|
|
138
|
-
return 'A';
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Registered — check if pending hash differs from on-chain
|
|
142
|
-
if (pendingHash !== onChainHash) {
|
|
143
|
-
// Config differs — need to publish
|
|
144
|
-
return onChainState.listed ? 'B2' : 'B1';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Hash matches — stale pending
|
|
148
|
-
return 'C';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function validateStateDirectory(stateDirectory: string): void {
|
|
152
|
-
// Check for path traversal characters
|
|
153
|
-
if (stateDirectory.includes('..')) {
|
|
154
|
-
throw new Error(
|
|
155
|
-
'stateDirectory cannot contain path traversal characters (..). ' +
|
|
156
|
-
'Use absolute paths only for security.'
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Resolve the path to get the absolute path
|
|
161
|
-
const resolvedPath = path.resolve(stateDirectory);
|
|
162
|
-
|
|
163
|
-
// If path exists, reject symlinks and use realpath for boundary checks.
|
|
164
|
-
// This blocks symlink escapes like "~/project" -> "/etc".
|
|
165
|
-
let effectivePath = resolvedPath;
|
|
166
|
-
if (fs.existsSync(resolvedPath)) {
|
|
167
|
-
const st = fs.lstatSync(resolvedPath);
|
|
168
|
-
if (st.isSymbolicLink()) {
|
|
169
|
-
throw new Error(
|
|
170
|
-
'stateDirectory cannot be a symbolic link. ' +
|
|
171
|
-
`Path "${stateDirectory}" resolves to a symlink at "${resolvedPath}".`
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
if (!st.isDirectory()) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
`stateDirectory must be a directory. Path "${resolvedPath}" is not a directory.`
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
effectivePath = fs.realpathSync(resolvedPath);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Get safe base directories
|
|
183
|
-
const homeDir = os.homedir();
|
|
184
|
-
const cwd = process.cwd();
|
|
185
|
-
|
|
186
|
-
// SECURITY FIX (C-5): Use path.relative() instead of startsWith()
|
|
187
|
-
// to handle case-insensitive filesystems (macOS, Windows) correctly.
|
|
188
|
-
// path.relative() returns a path starting with '..' if target is outside base.
|
|
189
|
-
const relativeToHome = path.relative(homeDir, effectivePath);
|
|
190
|
-
const relativeToCwd = path.relative(cwd, effectivePath);
|
|
191
|
-
|
|
192
|
-
// Check if path escapes the boundary (starts with '..' or is absolute)
|
|
193
|
-
const isUnderHome = !relativeToHome.startsWith('..') && !path.isAbsolute(relativeToHome);
|
|
194
|
-
const isUnderCwd = !relativeToCwd.startsWith('..') && !path.isAbsolute(relativeToCwd);
|
|
195
|
-
|
|
196
|
-
if (!isUnderHome && !isUnderCwd) {
|
|
197
|
-
throw new Error(
|
|
198
|
-
'stateDirectory must be within home directory or current working directory. ' +
|
|
199
|
-
`Resolved path "${resolvedPath}" is outside allowed boundaries.`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Additional check: Ensure path doesn't contain null bytes (can bypass validation)
|
|
204
|
-
if (stateDirectory.includes('\0')) {
|
|
205
|
-
throw new Error('stateDirectory contains invalid null byte character');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ============================================================================
|
|
210
|
-
// Type Guards
|
|
211
|
-
// ============================================================================
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Type guard to check if runtime is MockRuntime.
|
|
215
|
-
*
|
|
216
|
-
* @param runtime - Runtime to check
|
|
217
|
-
* @returns True if runtime is IMockRuntime
|
|
218
|
-
*/
|
|
219
|
-
function isMockRuntime(runtime: IACTPRuntime): runtime is IMockRuntime {
|
|
220
|
-
return 'reset' in runtime && typeof (runtime as IMockRuntime).reset === 'function';
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ============================================================================
|
|
224
|
-
// Types
|
|
225
|
-
// ============================================================================
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Supported modes for ACTPClient.
|
|
229
|
-
*
|
|
230
|
-
* - `mock`: Local development mode with file-based state
|
|
231
|
-
* - `testnet`: Base Sepolia testnet (future)
|
|
232
|
-
* - `mainnet`: Base mainnet (future)
|
|
233
|
-
*/
|
|
234
|
-
export type ACTPClientMode = 'mock' | 'testnet' | 'mainnet';
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Configuration for creating an ACTPClient instance.
|
|
238
|
-
*/
|
|
239
|
-
export interface ACTPClientConfig {
|
|
240
|
-
/**
|
|
241
|
-
* Operating mode.
|
|
242
|
-
*
|
|
243
|
-
* - 'mock': Local development with file-based state
|
|
244
|
-
* - 'testnet': Base Sepolia testnet
|
|
245
|
-
* - 'mainnet': Base mainnet
|
|
246
|
-
*/
|
|
247
|
-
mode: ACTPClientMode;
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* The requester's Ethereum address.
|
|
251
|
-
*
|
|
252
|
-
* This address is used as the "from" address for all transactions
|
|
253
|
-
* created through this client instance.
|
|
254
|
-
*
|
|
255
|
-
* When wallet is 'auto', this is auto-derived from the Smart Wallet
|
|
256
|
-
* and does NOT need to be provided.
|
|
257
|
-
*
|
|
258
|
-
* @example '0x1111111111111111111111111111111111111111'
|
|
259
|
-
*/
|
|
260
|
-
requesterAddress?: string;
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* AIP-12: Wallet mode.
|
|
264
|
-
*
|
|
265
|
-
* - 'auto': CoinbaseSmartWallet + gas sponsorship (Tier 1, recommended).
|
|
266
|
-
* Requires CDP_API_KEY env var. Agent address = Smart Wallet address.
|
|
267
|
-
* - undefined: EOA wallet from privateKey (Tier 2, backward compatible).
|
|
268
|
-
*
|
|
269
|
-
* When 'auto', requesterAddress is derived from the Smart Wallet
|
|
270
|
-
* and does not need to be provided.
|
|
271
|
-
*/
|
|
272
|
-
wallet?: 'auto';
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Optional: Project root directory for mock state file storage.
|
|
276
|
-
*
|
|
277
|
-
* The state file will be stored at `{stateDirectory}/.actp/mock-state.json`.
|
|
278
|
-
* Defaults to current working directory.
|
|
279
|
-
* Only used when mode is 'mock'.
|
|
280
|
-
*/
|
|
281
|
-
stateDirectory?: string;
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Optional: Private key for signing transactions.
|
|
285
|
-
*
|
|
286
|
-
* Required when mode is 'testnet' or 'mainnet'.
|
|
287
|
-
* Not used in 'mock' mode.
|
|
288
|
-
*
|
|
289
|
-
* ⚠️ CRITICAL SECURITY WARNING (C-1):
|
|
290
|
-
*
|
|
291
|
-
* **NEVER use raw private keys in production environments**
|
|
292
|
-
*
|
|
293
|
-
* **Recommended Approaches:**
|
|
294
|
-
* 1. **Encrypted JSON Keystore** (ethers.Wallet.fromEncryptedJson) - Best for server-side
|
|
295
|
-
* - Stores key encrypted with password
|
|
296
|
-
* - Requires decryption at runtime (password from secure vault)
|
|
297
|
-
* - Standard Web3 format (compatible with MetaMask, Geth, etc.)
|
|
298
|
-
*
|
|
299
|
-
* 2. **Hardware Wallets** (Ledger/Trezor) - Best for high-value operations
|
|
300
|
-
* - Private key never leaves device
|
|
301
|
-
* - User confirmation for each transaction
|
|
302
|
-
* - Future SDK integration planned
|
|
303
|
-
*
|
|
304
|
-
* 3. **KMS/HSM Integration** - Best for enterprise deployment
|
|
305
|
-
* - AWS KMS, Google Cloud KMS, Azure Key Vault
|
|
306
|
-
* - Private key never accessible to application
|
|
307
|
-
* - Audit trail for all signing operations
|
|
308
|
-
*
|
|
309
|
-
* **Security Requirements:**
|
|
310
|
-
* - NEVER log this value or include in error messages
|
|
311
|
-
* - NEVER store in plaintext files or git repositories
|
|
312
|
-
* - NEVER expose in API responses or client-side code
|
|
313
|
-
* - NEVER hardcode in source code (use environment variables minimum)
|
|
314
|
-
* - ALWAYS use encrypted storage (keystore, KMS, hardware wallet)
|
|
315
|
-
* - ALWAYS rotate keys if compromise suspected
|
|
316
|
-
* - The ACTPClient toJSON() method excludes this field from serialization
|
|
317
|
-
*
|
|
318
|
-
* **Example (Encrypted Keystore):**
|
|
319
|
-
* ```typescript
|
|
320
|
-
* import { Wallet } from 'ethers';
|
|
321
|
-
* import fs from 'fs';
|
|
322
|
-
*
|
|
323
|
-
* // Load encrypted keystore
|
|
324
|
-
* const keystore = fs.readFileSync('path/to/keystore.json', 'utf8');
|
|
325
|
-
* const password = process.env.KEYSTORE_PASSWORD; // From secure vault, not .env file
|
|
326
|
-
* const wallet = await Wallet.fromEncryptedJson(keystore, password);
|
|
327
|
-
*
|
|
328
|
-
* // Use with ACTPClient
|
|
329
|
-
* const client = await ACTPClient.create({
|
|
330
|
-
* mode: 'testnet',
|
|
331
|
-
* requesterAddress: wallet.address,
|
|
332
|
-
* privateKey: wallet.privateKey, // Decrypted at runtime only
|
|
333
|
-
* rpcUrl: process.env.RPC_URL
|
|
334
|
-
* });
|
|
335
|
-
* ```
|
|
336
|
-
*/
|
|
337
|
-
privateKey?: string;
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Optional: RPC URL for blockchain connection.
|
|
341
|
-
*
|
|
342
|
-
* Required when mode is 'testnet' or 'mainnet'.
|
|
343
|
-
* Not used in 'mock' mode.
|
|
344
|
-
*
|
|
345
|
-
* @example 'https://base-sepolia.g.alchemy.com/v2/YOUR_KEY'
|
|
346
|
-
*/
|
|
347
|
-
rpcUrl?: string;
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Optional: Contract address overrides.
|
|
351
|
-
*
|
|
352
|
-
* Override default deployed contract addresses.
|
|
353
|
-
* Used in 'testnet' and 'mainnet' modes.
|
|
354
|
-
*/
|
|
355
|
-
contracts?: {
|
|
356
|
-
actpKernel?: string;
|
|
357
|
-
escrowVault?: string;
|
|
358
|
-
usdc?: string;
|
|
359
|
-
agentRegistry?: string;
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Optional: Gas settings for blockchain transactions.
|
|
364
|
-
*
|
|
365
|
-
* Used in 'testnet' and 'mainnet' modes.
|
|
366
|
-
*/
|
|
367
|
-
gasSettings?: {
|
|
368
|
-
maxFeePerGas?: bigint;
|
|
369
|
-
maxPriorityFeePerGas?: bigint;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Optional: EAS (Ethereum Attestation Service) configuration.
|
|
374
|
-
*
|
|
375
|
-
* SECURITY FIX (C-4): Required for attestation verification in testnet/mainnet modes.
|
|
376
|
-
* If not provided, attestation verification in releaseEscrow() will be skipped.
|
|
377
|
-
*
|
|
378
|
-
* Used in 'testnet' and 'mainnet' modes.
|
|
379
|
-
*/
|
|
380
|
-
easConfig?: EASConfig;
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Optional: Require valid EAS attestation before escrow release (blockchain modes).
|
|
384
|
-
*
|
|
385
|
-
* If true, `releaseEscrow()` will require an `attestationUID` and verify it on-chain via EAS.
|
|
386
|
-
*
|
|
387
|
-
* Default:
|
|
388
|
-
* - true when `easConfig` is provided
|
|
389
|
-
* - false otherwise
|
|
390
|
-
*/
|
|
391
|
-
requireAttestation?: boolean;
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Optional: Custom runtime instance.
|
|
395
|
-
*
|
|
396
|
-
* For advanced use cases where you want to provide your own
|
|
397
|
-
* runtime implementation (e.g., for testing with custom mocks).
|
|
398
|
-
*
|
|
399
|
-
* If provided, mode and stateDirectory are ignored.
|
|
400
|
-
*/
|
|
401
|
-
runtime?: IACTPRuntime;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Result of creating an ACTPClient.
|
|
406
|
-
*
|
|
407
|
-
* Contains metadata about the client initialization.
|
|
408
|
-
*/
|
|
409
|
-
export interface ACTPClientInfo {
|
|
410
|
-
/** Operating mode */
|
|
411
|
-
mode: ACTPClientMode;
|
|
412
|
-
/** Requester address */
|
|
413
|
-
address: string;
|
|
414
|
-
/** State directory (mock mode only) */
|
|
415
|
-
stateDirectory?: string;
|
|
416
|
-
/** Wallet tier ('auto' = Smart Wallet, 'eoa' = EOA, undefined = mock) */
|
|
417
|
-
walletTier?: 'auto' | 'eoa';
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// ============================================================================
|
|
421
|
-
// ACTPClient Class
|
|
422
|
-
// ============================================================================
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* ACTPClient - Main entry point for AGIRAILS SDK.
|
|
426
|
-
*
|
|
427
|
-
* This class provides a unified interface to the ACTP protocol through
|
|
428
|
-
* three abstraction levels, catering to developers with different needs:
|
|
429
|
-
*
|
|
430
|
-
* **Basic API** (`client.basic`):
|
|
431
|
-
* - Simplest possible interface
|
|
432
|
-
* - Smart defaults (24h deadline, 2-day dispute window)
|
|
433
|
-
* - User-friendly inputs (strings, no BigInt)
|
|
434
|
-
* - Perfect for: Quick prototypes, simple integrations
|
|
435
|
-
*
|
|
436
|
-
* **Standard API** (`client.standard`):
|
|
437
|
-
* - Explicit lifecycle methods
|
|
438
|
-
* - More control over transaction flow
|
|
439
|
-
* - Still with user-friendly input parsing
|
|
440
|
-
* - Perfect for: Production apps needing control
|
|
441
|
-
*
|
|
442
|
-
* **Advanced API** (`client.advanced`):
|
|
443
|
-
* - Direct access to protocol runtime
|
|
444
|
-
* - Full control over all parameters
|
|
445
|
-
* - Protocol-level types (BigInt, timestamps)
|
|
446
|
-
* - Perfect for: Power users, custom integrations
|
|
447
|
-
*
|
|
448
|
-
* @example
|
|
449
|
-
* ```typescript
|
|
450
|
-
* // Create client
|
|
451
|
-
* const client = await ACTPClient.create({
|
|
452
|
-
* mode: 'mock',
|
|
453
|
-
* requesterAddress: '0xRequester...',
|
|
454
|
-
* });
|
|
455
|
-
*
|
|
456
|
-
* // Three ways to create a transaction:
|
|
457
|
-
*
|
|
458
|
-
* // 1. Basic: One call does everything
|
|
459
|
-
* await client.basic.pay({ to: '0xProvider', amount: '100' });
|
|
460
|
-
*
|
|
461
|
-
* // 2. Standard: Explicit steps
|
|
462
|
-
* const txId = await client.standard.createTransaction({
|
|
463
|
-
* provider: '0xProvider',
|
|
464
|
-
* amount: '100',
|
|
465
|
-
* });
|
|
466
|
-
* await client.standard.linkEscrow(txId);
|
|
467
|
-
*
|
|
468
|
-
* // 3. Advanced: Full control
|
|
469
|
-
* const txId = await client.advanced.createTransaction({
|
|
470
|
-
* provider: '0xProvider',
|
|
471
|
-
* requester: '0xRequester',
|
|
472
|
-
* amount: '100000000', // wei
|
|
473
|
-
* deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
474
|
-
* disputeWindow: 172800,
|
|
475
|
-
* });
|
|
476
|
-
* ```
|
|
477
|
-
*/
|
|
478
|
-
export class ACTPClient {
|
|
479
|
-
/**
|
|
480
|
-
* Basic-level API.
|
|
481
|
-
*
|
|
482
|
-
* Provides the simplest interface for creating and checking transactions.
|
|
483
|
-
* Ideal for developers who want to "just make it work" without deep
|
|
484
|
-
* protocol knowledge.
|
|
485
|
-
*
|
|
486
|
-
* @example
|
|
487
|
-
* ```typescript
|
|
488
|
-
* const result = await client.basic.pay({
|
|
489
|
-
* to: '0xProvider...',
|
|
490
|
-
* amount: '100',
|
|
491
|
-
* });
|
|
492
|
-
* console.log('Transaction ID:', result.txId);
|
|
493
|
-
* console.log('State:', result.state); // 'COMMITTED'
|
|
494
|
-
* ```
|
|
495
|
-
*/
|
|
496
|
-
public readonly basic: BasicAdapter;
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Standard-level API.
|
|
500
|
-
*
|
|
501
|
-
* Provides explicit lifecycle methods for more control over
|
|
502
|
-
* the transaction flow while still offering user-friendly inputs.
|
|
503
|
-
*
|
|
504
|
-
* @example
|
|
505
|
-
* ```typescript
|
|
506
|
-
* // Create transaction (INITIATED state)
|
|
507
|
-
* const txId = await client.standard.createTransaction({
|
|
508
|
-
* provider: '0xProvider...',
|
|
509
|
-
* amount: '100',
|
|
510
|
-
* deadline: '+7d',
|
|
511
|
-
* });
|
|
512
|
-
*
|
|
513
|
-
* // Link escrow (auto-transitions to COMMITTED)
|
|
514
|
-
* await client.standard.linkEscrow(txId);
|
|
515
|
-
*
|
|
516
|
-
* // Transition to DELIVERED
|
|
517
|
-
* await client.standard.transitionState(txId, 'DELIVERED');
|
|
518
|
-
* ```
|
|
519
|
-
*/
|
|
520
|
-
public readonly standard: StandardAdapter;
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* The underlying runtime implementation.
|
|
524
|
-
*
|
|
525
|
-
* Direct access to the protocol runtime for advanced use cases.
|
|
526
|
-
* This is the same as `client.advanced`.
|
|
527
|
-
*/
|
|
528
|
-
public readonly runtime: IACTPRuntime;
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Client information (mode, address, etc.)
|
|
532
|
-
*/
|
|
533
|
-
public readonly info: ACTPClientInfo;
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* SECURITY FIX (C-4): EAS helper for attestation verification.
|
|
537
|
-
* Only available in testnet/mainnet modes when easConfig is provided.
|
|
538
|
-
*/
|
|
539
|
-
public readonly easHelper?: EASHelper;
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Adapter registry for managing available adapters.
|
|
543
|
-
*
|
|
544
|
-
* Used internally by the router but exposed for custom adapter registration.
|
|
545
|
-
*/
|
|
546
|
-
private readonly registry: AdapterRegistry;
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* Adapter router for intelligent adapter selection.
|
|
550
|
-
*
|
|
551
|
-
* Selects the best adapter based on payment parameters and metadata.
|
|
552
|
-
*/
|
|
553
|
-
private readonly router: AdapterRouter;
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* ERC-8004 Reputation Reporter (testnet/mainnet only).
|
|
557
|
-
* Used to report settlement outcomes to ERC-8004 Reputation Registry.
|
|
558
|
-
* @internal
|
|
559
|
-
*/
|
|
560
|
-
private readonly reputationReporter?: ReputationReporter;
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* AIP-12: Wallet provider (Tier 1 Auto or Tier 2 EOA).
|
|
564
|
-
* Only set in testnet/mainnet modes.
|
|
565
|
-
* @internal
|
|
566
|
-
*/
|
|
567
|
-
private readonly walletProvider?: IWalletProvider;
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Lazy Publish: Current activation scenario.
|
|
571
|
-
* Set during create(), consumed during first payACTPBatched().
|
|
572
|
-
* @internal
|
|
573
|
-
*/
|
|
574
|
-
private lazyScenario: ActivationScenario = 'none';
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Lazy Publish: Cached pending publish data.
|
|
578
|
-
* @internal
|
|
579
|
-
*/
|
|
580
|
-
private pendingPublish: PendingPublish | null = null;
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* AgentRegistry address (for lazy activation calls).
|
|
584
|
-
* @internal
|
|
585
|
-
*/
|
|
586
|
-
private agentRegistryAddress?: string;
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* Network identifier (e.g. 'base-sepolia', 'base-mainnet').
|
|
590
|
-
* Used for chain-scoped pending-publish file operations.
|
|
591
|
-
* @internal
|
|
592
|
-
*/
|
|
593
|
-
private networkId?: string;
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Whether the pending publish config is stale (AGIRAILS.md changed since last publish).
|
|
597
|
-
* When true, getActivationCalls() returns empty to prevent stale config going on-chain.
|
|
598
|
-
* @internal
|
|
599
|
-
*/
|
|
600
|
-
private pendingIsStale = false;
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Private constructor - use ACTPClient.create() factory method.
|
|
604
|
-
*/
|
|
605
|
-
private constructor(
|
|
606
|
-
runtime: IACTPRuntime,
|
|
607
|
-
requesterAddress: string,
|
|
608
|
-
info: ACTPClientInfo,
|
|
609
|
-
easHelper?: EASHelper,
|
|
610
|
-
erc8004Bridge?: ERC8004Bridge,
|
|
611
|
-
reputationReporter?: ReputationReporter,
|
|
612
|
-
walletProvider?: IWalletProvider,
|
|
613
|
-
contractAddresses?: { usdc: string; actpKernel: string; escrowVault: string },
|
|
614
|
-
lazyScenario: ActivationScenario = 'none',
|
|
615
|
-
pendingPublish: PendingPublish | null = null,
|
|
616
|
-
agentRegistryAddress?: string,
|
|
617
|
-
networkId?: string,
|
|
618
|
-
) {
|
|
619
|
-
this.runtime = runtime;
|
|
620
|
-
this.info = info;
|
|
621
|
-
this.easHelper = easHelper;
|
|
622
|
-
this.reputationReporter = reputationReporter;
|
|
623
|
-
this.walletProvider = walletProvider;
|
|
624
|
-
this.lazyScenario = lazyScenario;
|
|
625
|
-
this.pendingPublish = pendingPublish;
|
|
626
|
-
this.agentRegistryAddress = agentRegistryAddress;
|
|
627
|
-
this.networkId = networkId;
|
|
628
|
-
this.basic = new BasicAdapter(runtime, requesterAddress, easHelper, walletProvider, contractAddresses, this);
|
|
629
|
-
this.standard = new StandardAdapter(runtime, requesterAddress, easHelper);
|
|
630
|
-
|
|
631
|
-
// Initialize registry and router
|
|
632
|
-
this.registry = new AdapterRegistry();
|
|
633
|
-
this.registry.register(this.basic);
|
|
634
|
-
this.registry.register(this.standard);
|
|
635
|
-
this.router = new AdapterRouter(this.registry, erc8004Bridge);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// ==========================================================================
|
|
639
|
-
// Factory Method
|
|
640
|
-
// ==========================================================================
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Creates a new ACTPClient instance.
|
|
644
|
-
*
|
|
645
|
-
* This is the primary way to instantiate an ACTPClient.
|
|
646
|
-
* It handles runtime initialization based on the specified mode.
|
|
647
|
-
*
|
|
648
|
-
* @param config - Client configuration
|
|
649
|
-
* @returns Promise resolving to initialized ACTPClient
|
|
650
|
-
* @throws {Error} If mode is not supported (only 'mock' currently)
|
|
651
|
-
*
|
|
652
|
-
* @example
|
|
653
|
-
* ```typescript
|
|
654
|
-
* // Mock mode (local development)
|
|
655
|
-
* const client = await ACTPClient.create({
|
|
656
|
-
* mode: 'mock',
|
|
657
|
-
* requesterAddress: '0x1234...',
|
|
658
|
-
* });
|
|
659
|
-
*
|
|
660
|
-
* // Mock mode with custom state directory
|
|
661
|
-
* const client = await ACTPClient.create({
|
|
662
|
-
* mode: 'mock',
|
|
663
|
-
* requesterAddress: '0x1234...',
|
|
664
|
-
* stateDirectory: '/custom/path/.actp',
|
|
665
|
-
* });
|
|
666
|
-
*
|
|
667
|
-
* // Custom runtime (for testing)
|
|
668
|
-
* const customRuntime = new MockRuntime();
|
|
669
|
-
* const client = await ACTPClient.create({
|
|
670
|
-
* mode: 'mock',
|
|
671
|
-
* requesterAddress: '0x1234...',
|
|
672
|
-
* runtime: customRuntime,
|
|
673
|
-
* });
|
|
674
|
-
* ```
|
|
675
|
-
*/
|
|
676
|
-
static async create(config: ACTPClientConfig): Promise<ACTPClient> {
|
|
677
|
-
let runtime: IACTPRuntime;
|
|
678
|
-
let stateDirectory: string | undefined;
|
|
679
|
-
let easHelper: EASHelper | undefined;
|
|
680
|
-
let erc8004Bridge: ERC8004Bridge | undefined;
|
|
681
|
-
let reputationReporter: ReputationReporter | undefined;
|
|
682
|
-
let walletProvider: IWalletProvider | undefined;
|
|
683
|
-
let requesterAddress: string;
|
|
684
|
-
let contractAddresses: { usdc: string; actpKernel: string; escrowVault: string } | undefined;
|
|
685
|
-
let lazyScenario: ActivationScenario = 'none';
|
|
686
|
-
let lazyPending: PendingPublish | null = null;
|
|
687
|
-
let registryAddr: string | undefined;
|
|
688
|
-
let networkId: string | undefined;
|
|
689
|
-
|
|
690
|
-
// If custom runtime provided, use it directly
|
|
691
|
-
if (config.runtime) {
|
|
692
|
-
// Custom runtime: requesterAddress is mandatory
|
|
693
|
-
if (!config.requesterAddress) {
|
|
694
|
-
throw new Error('requesterAddress is required when providing a custom runtime');
|
|
695
|
-
}
|
|
696
|
-
if (!/^0x[a-fA-F0-9]{40}$/.test(config.requesterAddress)) {
|
|
697
|
-
throw new Error(
|
|
698
|
-
`Invalid requesterAddress: "${config.requesterAddress}". ` +
|
|
699
|
-
'Must be a valid Ethereum address (0x-prefixed, 40 hex chars)'
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
requesterAddress = config.requesterAddress;
|
|
703
|
-
runtime = config.runtime;
|
|
704
|
-
} else {
|
|
705
|
-
// Initialize runtime based on mode
|
|
706
|
-
switch (config.mode) {
|
|
707
|
-
case 'mock': {
|
|
708
|
-
// Mock mode: requesterAddress is optional (auto-generate if missing)
|
|
709
|
-
if (config.requesterAddress) {
|
|
710
|
-
if (!/^0x[a-fA-F0-9]{40}$/.test(config.requesterAddress)) {
|
|
711
|
-
throw new Error(
|
|
712
|
-
`Invalid requesterAddress: "${config.requesterAddress}". ` +
|
|
713
|
-
'Must be a valid Ethereum address (0x-prefixed, 40 hex chars)'
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
requesterAddress = config.requesterAddress;
|
|
717
|
-
} else {
|
|
718
|
-
requesterAddress = ethers.Wallet.createRandom().address;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// SECURITY FIX: Enhanced path validation to prevent path traversal attacks
|
|
722
|
-
if (config.stateDirectory) {
|
|
723
|
-
validateStateDirectory(config.stateDirectory);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// MockStateManager takes projectRoot as string parameter
|
|
727
|
-
const stateManager = new MockStateManager(config.stateDirectory);
|
|
728
|
-
runtime = new MockRuntime(stateManager);
|
|
729
|
-
stateDirectory = config.stateDirectory;
|
|
730
|
-
// EASHelper not needed in mock mode
|
|
731
|
-
break;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
case 'testnet':
|
|
735
|
-
case 'mainnet': {
|
|
736
|
-
// Auto-detect private key from keystore / env var if not provided
|
|
737
|
-
if (!config.privateKey) {
|
|
738
|
-
const { resolvePrivateKey } = await import('./wallet/keystore');
|
|
739
|
-
const resolved = await resolvePrivateKey(config.stateDirectory, { network: config.mode });
|
|
740
|
-
if (resolved) {
|
|
741
|
-
config = { ...config, privateKey: resolved };
|
|
742
|
-
} else {
|
|
743
|
-
throw new Error(
|
|
744
|
-
`No wallet found for ${config.mode} mode.\n\n` +
|
|
745
|
-
'Provide a private key via one of:\n' +
|
|
746
|
-
' 1. ACTP_KEY_PASSWORD env var + .actp/keystore.json (recommended)\n' +
|
|
747
|
-
' 2. ACTP_PRIVATE_KEY env var\n' +
|
|
748
|
-
' 3. privateKey option in ACTPClient.create()\n' +
|
|
749
|
-
' 4. Run "actp publish" to generate a wallet automatically'
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Map mode to network config
|
|
755
|
-
const network = config.mode === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
756
|
-
networkId = network;
|
|
757
|
-
const networkConfig = getNetwork(network);
|
|
758
|
-
|
|
759
|
-
// Default RPC URL from network config if not provided
|
|
760
|
-
const rpcUrl = config.rpcUrl ?? networkConfig.rpcUrl;
|
|
761
|
-
|
|
762
|
-
// Optional persistent state directory
|
|
763
|
-
if (config.stateDirectory) {
|
|
764
|
-
validateStateDirectory(config.stateDirectory);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Create ethers provider and signer
|
|
768
|
-
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
769
|
-
const privateKey = config.privateKey!; // Guaranteed by auto-detect above
|
|
770
|
-
const signer = new ethers.Wallet(privateKey, provider);
|
|
771
|
-
|
|
772
|
-
// ====================================================================
|
|
773
|
-
// AIP-12: Wallet Provider Selection
|
|
774
|
-
// ====================================================================
|
|
775
|
-
if (config.wallet === 'auto') {
|
|
776
|
-
// Tier 1: CoinbaseSmartWallet + gasless transactions
|
|
777
|
-
if (!networkConfig.aa) {
|
|
778
|
-
throw new Error(
|
|
779
|
-
`AA configuration not available for ${config.mode} mode. ` +
|
|
780
|
-
'Check that networks.ts has aa config for this network.'
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// Validate that bundler/paymaster URLs have actual API keys
|
|
785
|
-
const cdpBundlerUrl = networkConfig.aa.bundlerUrls.coinbase;
|
|
786
|
-
const hasPimlico = !!networkConfig.aa.bundlerUrls.pimlico;
|
|
787
|
-
if (cdpBundlerUrl.endsWith('/') && !hasPimlico) {
|
|
788
|
-
throw new Error(
|
|
789
|
-
'CDP_API_KEY is required for gas-sponsored transactions.\n\n' +
|
|
790
|
-
'Set up your API key:\n' +
|
|
791
|
-
' 1. Visit https://portal.cdp.coinbase.com/\n' +
|
|
792
|
-
' 2. Create a new API key\n' +
|
|
793
|
-
' 3. export CDP_API_KEY="your-key-here"\n\n' +
|
|
794
|
-
'Or set PIMLICO_API_KEY as an alternative bundler/paymaster.\n' +
|
|
795
|
-
'Or use wallet: undefined for traditional EOA transactions (requires ETH for gas).'
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
const autoWallet = await AutoWalletProvider.create({
|
|
800
|
-
signer,
|
|
801
|
-
provider,
|
|
802
|
-
chainId: networkConfig.chainId,
|
|
803
|
-
actpKernelAddress: config.contracts?.actpKernel ?? networkConfig.contracts.actpKernel,
|
|
804
|
-
bundler: {
|
|
805
|
-
primaryUrl: networkConfig.aa.bundlerUrls.coinbase,
|
|
806
|
-
backupUrl: networkConfig.aa.bundlerUrls.pimlico,
|
|
807
|
-
},
|
|
808
|
-
paymaster: {
|
|
809
|
-
primaryUrl: networkConfig.aa.paymasterUrls.coinbase,
|
|
810
|
-
backupUrl: networkConfig.aa.paymasterUrls.pimlico,
|
|
811
|
-
},
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
// Check AgentRegistry + Lazy Publish scenario
|
|
815
|
-
const smartWalletAddress = autoWallet.getAddress();
|
|
816
|
-
registryAddr = config.contracts?.agentRegistry
|
|
817
|
-
?? networkConfig.contracts.agentRegistry;
|
|
818
|
-
|
|
819
|
-
// Load pending publish (may be null) — chain-scoped
|
|
820
|
-
try {
|
|
821
|
-
lazyPending = loadPendingPublish(network);
|
|
822
|
-
} catch {
|
|
823
|
-
// Ignore file read errors
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
let useAutoWallet = false;
|
|
827
|
-
|
|
828
|
-
if (registryAddr) {
|
|
829
|
-
try {
|
|
830
|
-
const onChainState = await getOnChainAgentState(
|
|
831
|
-
provider, registryAddr, smartWalletAddress
|
|
832
|
-
);
|
|
833
|
-
lazyScenario = detectLazyPublishScenario(onChainState, lazyPending);
|
|
834
|
-
|
|
835
|
-
// Scenario C: stale pending — delete immediately
|
|
836
|
-
if (lazyScenario === 'C') {
|
|
837
|
-
deletePendingPublish(network);
|
|
838
|
-
lazyPending = null;
|
|
839
|
-
lazyScenario = 'none';
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Gate: configHash != ZERO || hasPendingPublish → use AutoWallet
|
|
843
|
-
const hasOnChainConfig = onChainState.configHash !== ZERO_HASH;
|
|
844
|
-
const hasPendingPublish = lazyPending !== null;
|
|
845
|
-
|
|
846
|
-
if (hasOnChainConfig || hasPendingPublish) {
|
|
847
|
-
useAutoWallet = true;
|
|
848
|
-
}
|
|
849
|
-
} catch {
|
|
850
|
-
// Registry check failed (e.g. RPC down).
|
|
851
|
-
// Fail-open only if pending publish exists (agent did `actp publish` → legitimate intent).
|
|
852
|
-
// Fail-closed otherwise to prevent unregistered agents getting free gas.
|
|
853
|
-
if (lazyPending) {
|
|
854
|
-
useAutoWallet = true;
|
|
855
|
-
sdkLogger.warn('AgentRegistry check failed, but pending publish found — proceeding with AA.');
|
|
856
|
-
} else {
|
|
857
|
-
sdkLogger.warn('AgentRegistry check failed and no pending publish — falling back to EOA.');
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
} else {
|
|
861
|
-
// No registry deployed — skip check (early testnet)
|
|
862
|
-
useAutoWallet = true;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (useAutoWallet) {
|
|
866
|
-
walletProvider = autoWallet;
|
|
867
|
-
requesterAddress = smartWalletAddress;
|
|
868
|
-
} else {
|
|
869
|
-
// Not published and no pending — fall back to EOA with warning
|
|
870
|
-
sdkLogger.warn(
|
|
871
|
-
'Agent not published on AgentRegistry and no pending publish found. ' +
|
|
872
|
-
'Falling back to EOA wallet (gas not sponsored). ' +
|
|
873
|
-
'Run "actp publish" for gas-free transactions.'
|
|
874
|
-
);
|
|
875
|
-
walletProvider = new EOAWalletProvider(signer, networkConfig.chainId);
|
|
876
|
-
requesterAddress = signer.address;
|
|
877
|
-
// Reset since we're not using auto wallet
|
|
878
|
-
lazyScenario = 'none';
|
|
879
|
-
lazyPending = null;
|
|
880
|
-
}
|
|
881
|
-
} else {
|
|
882
|
-
// Tier 2: EOA Wallet (backward compatible)
|
|
883
|
-
walletProvider = new EOAWalletProvider(signer, networkConfig.chainId);
|
|
884
|
-
requesterAddress = config.requesterAddress ?? signer.address;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// Validate derived/provided address
|
|
888
|
-
if (!/^0x[a-fA-F0-9]{40}$/.test(requesterAddress)) {
|
|
889
|
-
throw new Error(
|
|
890
|
-
`Invalid requesterAddress: "${requesterAddress}". ` +
|
|
891
|
-
'Must be a valid Ethereum address (0x-prefixed, 40 hex chars)'
|
|
892
|
-
);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const requireAttestation = config.requireAttestation ?? Boolean(config.easConfig);
|
|
896
|
-
|
|
897
|
-
// Create BlockchainRuntime
|
|
898
|
-
const blockchainRuntime = new BlockchainRuntime({
|
|
899
|
-
network,
|
|
900
|
-
signer,
|
|
901
|
-
provider,
|
|
902
|
-
contracts: config.contracts,
|
|
903
|
-
gasSettings: config.gasSettings,
|
|
904
|
-
easConfig: config.easConfig,
|
|
905
|
-
requireAttestation,
|
|
906
|
-
stateDirectory: config.stateDirectory,
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
// Initialize async components
|
|
910
|
-
await blockchainRuntime.initialize();
|
|
911
|
-
|
|
912
|
-
runtime = blockchainRuntime;
|
|
913
|
-
|
|
914
|
-
// SECURITY FIX (C-4): Use the runtime's initialized EASHelper so
|
|
915
|
-
// adapters and runtime share the same tracker + verification logic.
|
|
916
|
-
if (config.easConfig) {
|
|
917
|
-
easHelper = blockchainRuntime.getEASHelper();
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// ERC-8004 INTEGRATION: Create bridge for agent ID resolution
|
|
921
|
-
const erc8004Network: ERC8004Network =
|
|
922
|
-
config.mode === 'testnet' ? 'base-sepolia' : 'base';
|
|
923
|
-
erc8004Bridge = new ERC8004Bridge({
|
|
924
|
-
network: erc8004Network,
|
|
925
|
-
rpcUrl,
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
// ERC-8004 REPUTATION: Create reporter for settlement outcome reporting
|
|
929
|
-
reputationReporter = new ReputationReporter({
|
|
930
|
-
network: erc8004Network,
|
|
931
|
-
signer,
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
// AIP-12: Contract addresses for AA batched payments
|
|
935
|
-
contractAddresses = {
|
|
936
|
-
usdc: config.contracts?.usdc ?? networkConfig.contracts.usdc,
|
|
937
|
-
actpKernel: config.contracts?.actpKernel ?? networkConfig.contracts.actpKernel,
|
|
938
|
-
escrowVault: config.contracts?.escrowVault ?? networkConfig.contracts.escrowVault,
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
break;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
default:
|
|
945
|
-
throw new Error(
|
|
946
|
-
`Unknown mode: "${config.mode}". ` +
|
|
947
|
-
'Supported modes: "mock", "testnet", "mainnet"'
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// Normalize address to lowercase for consistency
|
|
953
|
-
const normalizedAddress = requesterAddress.toLowerCase();
|
|
954
|
-
|
|
955
|
-
const info: ACTPClientInfo = {
|
|
956
|
-
mode: config.mode,
|
|
957
|
-
address: normalizedAddress,
|
|
958
|
-
stateDirectory,
|
|
959
|
-
walletTier: walletProvider?.getWalletInfo().tier,
|
|
960
|
-
};
|
|
961
|
-
|
|
962
|
-
// Staleness check: recompute hash if AGIRAILS.md exists and we have a pending publish
|
|
963
|
-
let pendingIsStale = false;
|
|
964
|
-
if (lazyPending && lazyScenario !== 'none' && lazyScenario !== 'C') {
|
|
965
|
-
try {
|
|
966
|
-
const mdPath = path.join(process.cwd(), 'AGIRAILS.md');
|
|
967
|
-
if (fs.existsSync(mdPath)) {
|
|
968
|
-
const { computeConfigHash } = await import('./config/agirailsmd');
|
|
969
|
-
const content = fs.readFileSync(mdPath, 'utf-8');
|
|
970
|
-
const { configHash: currentHash } = computeConfigHash(content);
|
|
971
|
-
if (currentHash !== lazyPending.configHash) {
|
|
972
|
-
pendingIsStale = true;
|
|
973
|
-
sdkLogger.warn(
|
|
974
|
-
'AGIRAILS.md changed since last publish. Activation skipped. ' +
|
|
975
|
-
'Run "actp publish" to update.'
|
|
976
|
-
);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
} catch {
|
|
980
|
-
// Best-effort: staleness check should not block operation
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Pass wallet provider, contract addresses, and lazy publish state to constructor
|
|
985
|
-
const client = new ACTPClient(
|
|
986
|
-
runtime, normalizedAddress, info, easHelper,
|
|
987
|
-
erc8004Bridge, reputationReporter, walletProvider, contractAddresses,
|
|
988
|
-
lazyScenario, lazyPending, registryAddr, networkId,
|
|
989
|
-
);
|
|
990
|
-
client.pendingIsStale = pendingIsStale;
|
|
991
|
-
|
|
992
|
-
// Drift detection: non-blocking check for AGIRAILS.md sync status
|
|
993
|
-
if (config.mode !== 'mock') {
|
|
994
|
-
client.checkConfigDrift(config).catch(() => {
|
|
995
|
-
// Silently ignore drift check errors — non-critical
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
return client;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// ==========================================================================
|
|
1003
|
-
// Public Methods
|
|
1004
|
-
// ==========================================================================
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Advanced-level API.
|
|
1008
|
-
*
|
|
1009
|
-
* Provides direct access to the underlying protocol runtime.
|
|
1010
|
-
* Use this when you need full control over all parameters.
|
|
1011
|
-
*
|
|
1012
|
-
* This is the same as accessing `client.runtime` directly.
|
|
1013
|
-
*
|
|
1014
|
-
* @example
|
|
1015
|
-
* ```typescript
|
|
1016
|
-
* // Direct runtime access
|
|
1017
|
-
* const txId = await client.advanced.createTransaction({
|
|
1018
|
-
* provider: '0xProvider',
|
|
1019
|
-
* requester: '0xRequester',
|
|
1020
|
-
* amount: '100000000', // wei
|
|
1021
|
-
* deadline: Math.floor(Date.now() / 1000) + 86400,
|
|
1022
|
-
* });
|
|
1023
|
-
*
|
|
1024
|
-
* // Get transaction details
|
|
1025
|
-
* const tx = await client.advanced.getTransaction(txId);
|
|
1026
|
-
*
|
|
1027
|
-
* // Time manipulation (mock mode only - requires IMockRuntime cast)
|
|
1028
|
-
* import { IMockRuntime } from './runtime/IACTPRuntime';
|
|
1029
|
-
* if (client.getMode() === 'mock') {
|
|
1030
|
-
* (client.advanced as IMockRuntime).time.advanceTime(3600); // Advance 1 hour
|
|
1031
|
-
* }
|
|
1032
|
-
* ```
|
|
1033
|
-
*/
|
|
1034
|
-
get advanced(): IACTPRuntime {
|
|
1035
|
-
return this.runtime;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* Gets the requester's Ethereum address.
|
|
1040
|
-
*
|
|
1041
|
-
* This is the address used as the "from" address for all transactions
|
|
1042
|
-
* created through this client.
|
|
1043
|
-
*
|
|
1044
|
-
* @returns The requester's Ethereum address (normalized to lowercase)
|
|
1045
|
-
*
|
|
1046
|
-
* @example
|
|
1047
|
-
* ```typescript
|
|
1048
|
-
* const address = client.getAddress();
|
|
1049
|
-
* console.log('My address:', address);
|
|
1050
|
-
* // '0x1111111111111111111111111111111111111111'
|
|
1051
|
-
* ```
|
|
1052
|
-
*/
|
|
1053
|
-
getAddress(): string {
|
|
1054
|
-
return this.info.address;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
* Gets the current operating mode.
|
|
1059
|
-
*
|
|
1060
|
-
* @returns The client's operating mode ('mock', 'testnet', or 'mainnet')
|
|
1061
|
-
*
|
|
1062
|
-
* @example
|
|
1063
|
-
* ```typescript
|
|
1064
|
-
* if (client.getMode() === 'mock') {
|
|
1065
|
-
* console.log('Running in local development mode');
|
|
1066
|
-
* }
|
|
1067
|
-
* ```
|
|
1068
|
-
*/
|
|
1069
|
-
getMode(): ACTPClientMode {
|
|
1070
|
-
return this.info.mode;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
/**
|
|
1074
|
-
* Resets the mock state to default.
|
|
1075
|
-
*
|
|
1076
|
-
* Only available in mock mode. Clears all transactions, escrows,
|
|
1077
|
-
* and accounts, resetting to a fresh state.
|
|
1078
|
-
*
|
|
1079
|
-
* @throws {Error} If not in mock mode or runtime doesn't support reset
|
|
1080
|
-
*
|
|
1081
|
-
* @example
|
|
1082
|
-
* ```typescript
|
|
1083
|
-
* // Reset state between test runs
|
|
1084
|
-
* await client.reset();
|
|
1085
|
-
* ```
|
|
1086
|
-
*/
|
|
1087
|
-
async reset(): Promise<void> {
|
|
1088
|
-
if (this.info.mode !== 'mock') {
|
|
1089
|
-
throw new Error(
|
|
1090
|
-
`reset() is only available in mock mode. Current mode: "${this.info.mode}"`
|
|
1091
|
-
);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
if (!isMockRuntime(this.runtime)) {
|
|
1095
|
-
throw new Error('Runtime does not support reset operation');
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
await this.runtime.reset();
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
/**
|
|
1102
|
-
* Custom JSON serialization to prevent private key exposure.
|
|
1103
|
-
*
|
|
1104
|
-
* SECURITY FIX (HIGH-4): Prevents accidental private key logging
|
|
1105
|
-
* when ACTPClient instance is serialized (e.g., JSON.stringify, console.log).
|
|
1106
|
-
*
|
|
1107
|
-
* @returns Safe serializable object with sensitive data removed
|
|
1108
|
-
*/
|
|
1109
|
-
toJSON(): object {
|
|
1110
|
-
return {
|
|
1111
|
-
mode: this.info.mode,
|
|
1112
|
-
address: this.info.address,
|
|
1113
|
-
stateDirectory: this.info.stateDirectory,
|
|
1114
|
-
isInitialized: true,
|
|
1115
|
-
// Explicitly exclude: privateKey, signer, provider internals
|
|
1116
|
-
_warning: 'Sensitive data (privateKey, signer) excluded for security',
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
/**
|
|
1121
|
-
* Custom string representation for debugging.
|
|
1122
|
-
*
|
|
1123
|
-
* SECURITY FIX (HIGH-4): Prevents private key exposure in logs.
|
|
1124
|
-
*/
|
|
1125
|
-
toString(): string {
|
|
1126
|
-
return `ACTPClient(mode=${this.info.mode}, address=${this.info.address})`;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
/**
|
|
1130
|
-
* Custom inspect for Node.js util.inspect (console.log).
|
|
1131
|
-
*
|
|
1132
|
-
* SECURITY FIX (HIGH-4): Prevents private key exposure in console output.
|
|
1133
|
-
*/
|
|
1134
|
-
[Symbol.for('nodejs.util.inspect.custom')](): string {
|
|
1135
|
-
return this.toString();
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
/**
|
|
1139
|
-
* Mints USDC tokens to an address.
|
|
1140
|
-
*
|
|
1141
|
-
* Only available in mock mode. Useful for testing scenarios
|
|
1142
|
-
* where you need to fund accounts.
|
|
1143
|
-
*
|
|
1144
|
-
* @param address - Address to mint tokens to
|
|
1145
|
-
* @param amount - Amount to mint (in USDC wei, e.g., '1000000' for 1 USDC)
|
|
1146
|
-
* @throws {Error} If not in mock mode or runtime doesn't support mintTokens
|
|
1147
|
-
*
|
|
1148
|
-
* @example
|
|
1149
|
-
* ```typescript
|
|
1150
|
-
* // Mint 1000 USDC to the requester
|
|
1151
|
-
* await client.mintTokens(client.getAddress(), '1000000000'); // 1000 * 10^6
|
|
1152
|
-
* ```
|
|
1153
|
-
*/
|
|
1154
|
-
async mintTokens(address: string, amount: string): Promise<void> {
|
|
1155
|
-
if (this.info.mode !== 'mock') {
|
|
1156
|
-
throw new Error(
|
|
1157
|
-
`mintTokens() is only available in mock mode. Current mode: "${this.info.mode}"`
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (!isMockRuntime(this.runtime)) {
|
|
1162
|
-
throw new Error('Runtime does not support mintTokens operation');
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
await this.runtime.mintTokens(address, amount);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* Gets the USDC balance of an address.
|
|
1170
|
-
*
|
|
1171
|
-
* @param address - Address to check balance for
|
|
1172
|
-
* @returns Promise resolving to balance in USDC wei
|
|
1173
|
-
* @throws {Error} If runtime doesn't support getBalance
|
|
1174
|
-
*
|
|
1175
|
-
* @example
|
|
1176
|
-
* ```typescript
|
|
1177
|
-
* const balance = await client.getBalance(client.getAddress());
|
|
1178
|
-
* console.log('Balance:', balance); // '1000000000' (1000 USDC)
|
|
1179
|
-
* ```
|
|
1180
|
-
*/
|
|
1181
|
-
async getBalance(address: string): Promise<string> {
|
|
1182
|
-
// Both MockRuntime and BlockchainRuntime support getBalance
|
|
1183
|
-
if ('getBalance' in this.runtime && typeof (this.runtime as any).getBalance === 'function') {
|
|
1184
|
-
return (this.runtime as any).getBalance(address);
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
throw new Error('Runtime does not support getBalance operation');
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
// ==========================================================================
|
|
1191
|
-
// Unified Payment API (Router-based)
|
|
1192
|
-
// ==========================================================================
|
|
1193
|
-
|
|
1194
|
-
/**
|
|
1195
|
-
* Unified pay method - auto-selects the best adapter.
|
|
1196
|
-
*
|
|
1197
|
-
* This is the recommended way to initiate payments. The router
|
|
1198
|
-
* intelligently selects the appropriate adapter based on:
|
|
1199
|
-
* - Explicit adapter preference (metadata.preferredAdapter)
|
|
1200
|
-
* - Required capabilities (escrow, disputes)
|
|
1201
|
-
* - Recipient type (address vs HTTP endpoint)
|
|
1202
|
-
*
|
|
1203
|
-
* IMPORTANT: Returns with state=COMMITTED, NOT settled.
|
|
1204
|
-
* You MUST call the lifecycle methods to complete:
|
|
1205
|
-
*
|
|
1206
|
-
* ```typescript
|
|
1207
|
-
* const result = await client.pay({ to, amount });
|
|
1208
|
-
* // ... provider does work ...
|
|
1209
|
-
* await client.startWork(result.txId);
|
|
1210
|
-
* await client.deliver(result.txId);
|
|
1211
|
-
* // ... after dispute window ...
|
|
1212
|
-
* await client.release(result.escrowId!); // EXPLICIT release
|
|
1213
|
-
* ```
|
|
1214
|
-
*
|
|
1215
|
-
* @param params - Unified payment parameters
|
|
1216
|
-
* @returns Promise resolving to unified payment result
|
|
1217
|
-
* @throws {ValidationError} If params are invalid
|
|
1218
|
-
* @throws {Error} If no suitable adapter found
|
|
1219
|
-
*
|
|
1220
|
-
* @example
|
|
1221
|
-
* ```typescript
|
|
1222
|
-
* // Simple payment (uses basic adapter by default)
|
|
1223
|
-
* const result = await client.pay({
|
|
1224
|
-
* to: '0xProvider...',
|
|
1225
|
-
* amount: '100',
|
|
1226
|
-
* });
|
|
1227
|
-
*
|
|
1228
|
-
* // Require escrow (prefers standard adapter)
|
|
1229
|
-
* const result = await client.pay({
|
|
1230
|
-
* to: '0xProvider...',
|
|
1231
|
-
* amount: '100',
|
|
1232
|
-
* metadata: { requiresEscrow: true }
|
|
1233
|
-
* });
|
|
1234
|
-
*
|
|
1235
|
-
* // Explicit adapter selection
|
|
1236
|
-
* const result = await client.pay({
|
|
1237
|
-
* to: '0xProvider...',
|
|
1238
|
-
* amount: '100',
|
|
1239
|
-
* metadata: { preferredAdapter: 'standard' }
|
|
1240
|
-
* });
|
|
1241
|
-
* ```
|
|
1242
|
-
*/
|
|
1243
|
-
async pay(params: UnifiedPayParams): Promise<UnifiedPayResult> {
|
|
1244
|
-
const { adapter, resolvedParams } = await this.router.selectAndResolve(params);
|
|
1245
|
-
|
|
1246
|
-
// When a wallet provider with batched support is available (AA/Smart Wallet)
|
|
1247
|
-
// AND the target is an Ethereum address (not an x402 URL), route to BasicAdapter
|
|
1248
|
-
// which has the payACTPBatched path. StandardAdapter lacks batched support and
|
|
1249
|
-
// would send raw txs from the EOA signer, causing "Requester mismatch" on-chain.
|
|
1250
|
-
if (this.walletProvider?.payACTPBatched && this.basic.canHandle(resolvedParams)) {
|
|
1251
|
-
return this.basic.pay(resolvedParams);
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
return adapter.pay(resolvedParams);
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
/**
|
|
1258
|
-
* Get transaction status by ID.
|
|
1259
|
-
*
|
|
1260
|
-
* Returns current state plus action hints indicating
|
|
1261
|
-
* what operations are available.
|
|
1262
|
-
*
|
|
1263
|
-
* @param txId - Transaction ID
|
|
1264
|
-
* @returns Promise resolving to transaction status
|
|
1265
|
-
* @throws {Error} If transaction not found
|
|
1266
|
-
*
|
|
1267
|
-
* @example
|
|
1268
|
-
* ```typescript
|
|
1269
|
-
* const status = await client.getStatus(txId);
|
|
1270
|
-
* if (status.canRelease) {
|
|
1271
|
-
* await client.release(txId);
|
|
1272
|
-
* }
|
|
1273
|
-
* ```
|
|
1274
|
-
*/
|
|
1275
|
-
async getStatus(txId: string): Promise<TransactionStatus> {
|
|
1276
|
-
// Use standard adapter for status - it has access to all tx details
|
|
1277
|
-
return this.standard.getStatus(txId);
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
/**
|
|
1281
|
-
* Transition to IN_PROGRESS state (provider starts work).
|
|
1282
|
-
*
|
|
1283
|
-
* Must be called by provider after accepting the transaction.
|
|
1284
|
-
* ACTP requires this explicit transition before delivery.
|
|
1285
|
-
*
|
|
1286
|
-
* @param txId - Transaction ID
|
|
1287
|
-
* @throws {Error} If transaction not found or wrong state
|
|
1288
|
-
*
|
|
1289
|
-
* @example
|
|
1290
|
-
* ```typescript
|
|
1291
|
-
* // Provider acknowledges and starts work
|
|
1292
|
-
* await client.startWork(txId);
|
|
1293
|
-
* ```
|
|
1294
|
-
*/
|
|
1295
|
-
async startWork(txId: string): Promise<void> {
|
|
1296
|
-
await this.runtime.transitionState(txId, 'IN_PROGRESS');
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
/**
|
|
1300
|
-
* Transition to DELIVERED state (provider completes work).
|
|
1301
|
-
*
|
|
1302
|
-
* When no disputeWindowSeconds is provided, uses the transaction's actual
|
|
1303
|
-
* disputeWindow from creation time. This ensures consistency and prevents
|
|
1304
|
-
* mismatches between transaction creation and delivery.
|
|
1305
|
-
*
|
|
1306
|
-
* @param txId - Transaction ID
|
|
1307
|
-
* @param disputeWindowSeconds - Optional dispute window override in seconds.
|
|
1308
|
-
* If not provided, uses transaction's disputeWindow.
|
|
1309
|
-
* @throws {Error} If transaction not found or wrong state
|
|
1310
|
-
*
|
|
1311
|
-
* @example
|
|
1312
|
-
* ```typescript
|
|
1313
|
-
* // Use transaction's disputeWindow (recommended)
|
|
1314
|
-
* await client.deliver(txId);
|
|
1315
|
-
*
|
|
1316
|
-
* // Override with custom dispute window (use with caution)
|
|
1317
|
-
* await client.deliver(txId, 7200);
|
|
1318
|
-
* ```
|
|
1319
|
-
*/
|
|
1320
|
-
async deliver(txId: string, disputeWindowSeconds?: number): Promise<void> {
|
|
1321
|
-
// Fetch transaction
|
|
1322
|
-
const tx = await this.runtime.getTransaction(txId);
|
|
1323
|
-
if (!tx) {
|
|
1324
|
-
throw new Error(`Transaction ${txId} not found`);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// First ensure we're in IN_PROGRESS state
|
|
1328
|
-
if (tx.state === 'COMMITTED') {
|
|
1329
|
-
await this.runtime.transitionState(txId, 'IN_PROGRESS');
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// Use provided disputeWindow or fall back to transaction's disputeWindow
|
|
1333
|
-
const effectiveDisputeWindow = disputeWindowSeconds ?? tx.disputeWindow;
|
|
1334
|
-
|
|
1335
|
-
// Encode dispute window as proof
|
|
1336
|
-
const proof = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1337
|
-
['uint256'],
|
|
1338
|
-
[effectiveDisputeWindow]
|
|
1339
|
-
);
|
|
1340
|
-
|
|
1341
|
-
await this.runtime.transitionState(txId, 'DELIVERED', proof);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
/**
|
|
1345
|
-
* Release escrow funds (EXPLICIT settlement).
|
|
1346
|
-
*
|
|
1347
|
-
* MUST be called after dispute window expires or requester approves.
|
|
1348
|
-
* This is the ONLY way to settle - NO auto-settle.
|
|
1349
|
-
*
|
|
1350
|
-
* If ERC-8004 agent ID was set during transaction creation, this method
|
|
1351
|
-
* also reports the settlement to the ERC-8004 Reputation Registry.
|
|
1352
|
-
* Reputation reporting is non-blocking - failures don't affect settlement.
|
|
1353
|
-
*
|
|
1354
|
-
* @param escrowId - Escrow ID (usually same as txId)
|
|
1355
|
-
* @param attestationUID - Optional attestation UID for verification
|
|
1356
|
-
* @throws {Error} If escrow not found or dispute window active
|
|
1357
|
-
*
|
|
1358
|
-
* @example
|
|
1359
|
-
* ```typescript
|
|
1360
|
-
* // After dispute window expires
|
|
1361
|
-
* await client.release(result.escrowId!);
|
|
1362
|
-
* // Transaction is now SETTLED
|
|
1363
|
-
* // If ERC-8004 agent, reputation is automatically reported
|
|
1364
|
-
* ```
|
|
1365
|
-
*/
|
|
1366
|
-
async release(escrowId: string, attestationUID?: string): Promise<void> {
|
|
1367
|
-
// In ACTP, escrowId === txId
|
|
1368
|
-
const txId = escrowId;
|
|
1369
|
-
|
|
1370
|
-
// Get transaction to find agentId (for reputation reporting)
|
|
1371
|
-
const tx = await this.runtime.getTransaction(txId);
|
|
1372
|
-
const agentId = tx?.agentId;
|
|
1373
|
-
|
|
1374
|
-
// Release escrow (this is the critical operation)
|
|
1375
|
-
await this.runtime.releaseEscrow(escrowId, attestationUID);
|
|
1376
|
-
|
|
1377
|
-
// ERC-8004 REPUTATION: Report settlement if agent ID exists
|
|
1378
|
-
// Non-blocking - fire and forget (settlement already succeeded)
|
|
1379
|
-
if (this.reputationReporter && agentId && agentId !== '0') {
|
|
1380
|
-
// Don't await - reputation reporting shouldn't block the release
|
|
1381
|
-
this.reputationReporter
|
|
1382
|
-
.reportSettlement({
|
|
1383
|
-
agentId,
|
|
1384
|
-
txId,
|
|
1385
|
-
})
|
|
1386
|
-
.then((result) => {
|
|
1387
|
-
if (result) {
|
|
1388
|
-
sdkLogger.info(
|
|
1389
|
-
`[ERC8004] Settlement reported for agent ${agentId}: ${result.txHash}`
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
})
|
|
1393
|
-
.catch(() => {
|
|
1394
|
-
// Errors already logged by reporter - silently ignore here
|
|
1395
|
-
});
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Register a custom adapter.
|
|
1401
|
-
*
|
|
1402
|
-
* Allows adding custom payment adapters (e.g., x402, ERC-8004)
|
|
1403
|
-
* that will be considered during router selection.
|
|
1404
|
-
*
|
|
1405
|
-
* @param adapter - Adapter to register
|
|
1406
|
-
*
|
|
1407
|
-
* @example
|
|
1408
|
-
* ```typescript
|
|
1409
|
-
* // Register a custom x402 adapter
|
|
1410
|
-
* client.registerAdapter(new X402Adapter(requesterAddress, { expectedNetwork, transferFn }));
|
|
1411
|
-
* ```
|
|
1412
|
-
*/
|
|
1413
|
-
registerAdapter(adapter: IAdapter): void {
|
|
1414
|
-
this.registry.register(adapter);
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
/**
|
|
1418
|
-
* Get all registered adapter IDs.
|
|
1419
|
-
*
|
|
1420
|
-
* @returns Array of adapter IDs
|
|
1421
|
-
*
|
|
1422
|
-
* @example
|
|
1423
|
-
* ```typescript
|
|
1424
|
-
* const adapters = client.getRegisteredAdapters();
|
|
1425
|
-
* console.log(adapters); // ['basic', 'standard', 'x402']
|
|
1426
|
-
* ```
|
|
1427
|
-
*/
|
|
1428
|
-
getRegisteredAdapters(): string[] {
|
|
1429
|
-
return this.registry.getIds();
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
/**
|
|
1433
|
-
* Get the ERC-8004 Reputation Reporter instance.
|
|
1434
|
-
*
|
|
1435
|
-
* Only available in testnet/mainnet modes. Returns undefined in mock mode.
|
|
1436
|
-
* Use this for manual reputation reporting or checking stats.
|
|
1437
|
-
*
|
|
1438
|
-
* @returns ReputationReporter instance or undefined
|
|
1439
|
-
*
|
|
1440
|
-
* @example
|
|
1441
|
-
* ```typescript
|
|
1442
|
-
* const reporter = client.getReputationReporter();
|
|
1443
|
-
* if (reporter) {
|
|
1444
|
-
* // Check if already reported
|
|
1445
|
-
* const reported = reporter.isReported(txId);
|
|
1446
|
-
*
|
|
1447
|
-
* // Get agent reputation
|
|
1448
|
-
* const rep = await reporter.getAgentReputation('12345');
|
|
1449
|
-
* console.log(`Agent has ${rep?.count} reviews, score: ${rep?.score}`);
|
|
1450
|
-
* }
|
|
1451
|
-
* ```
|
|
1452
|
-
*/
|
|
1453
|
-
getReputationReporter(): ReputationReporter | undefined {
|
|
1454
|
-
return this.reputationReporter;
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
/**
|
|
1458
|
-
* AIP-12: Get the wallet provider instance.
|
|
1459
|
-
*
|
|
1460
|
-
* Only available in testnet/mainnet modes.
|
|
1461
|
-
* Returns undefined in mock mode.
|
|
1462
|
-
*
|
|
1463
|
-
* Use this for advanced operations like checking wallet info,
|
|
1464
|
-
* or sending custom batched transactions.
|
|
1465
|
-
*/
|
|
1466
|
-
getWalletProvider(): IWalletProvider | undefined {
|
|
1467
|
-
return this.walletProvider;
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
/**
|
|
1471
|
-
* Get activation calls for lazy publish.
|
|
1472
|
-
*
|
|
1473
|
-
* Returns SmartWalletCall[] to prepend to the first payment UserOp,
|
|
1474
|
-
* plus an onSuccess callback that deletes pending-publish.json.
|
|
1475
|
-
*
|
|
1476
|
-
* Returns empty calls if no activation is needed (scenario C/none).
|
|
1477
|
-
* @internal
|
|
1478
|
-
*/
|
|
1479
|
-
getActivationCalls(): { calls: SmartWalletCall[]; onSuccess: () => void } {
|
|
1480
|
-
if (this.lazyScenario === 'none' || this.lazyScenario === 'C' || !this.agentRegistryAddress) {
|
|
1481
|
-
return { calls: [], onSuccess: () => {} };
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
// Staleness check: AGIRAILS.md changed since last publish → skip activation
|
|
1485
|
-
if (this.pendingIsStale) {
|
|
1486
|
-
return { calls: [], onSuccess: () => {} };
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
const pending = this.pendingPublish;
|
|
1490
|
-
if (!pending) {
|
|
1491
|
-
return { calls: [], onSuccess: () => {} };
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
// Build activation batch params
|
|
1495
|
-
const params: import('./wallet/aa/TransactionBatcher').ActivationBatchParams = {
|
|
1496
|
-
scenario: this.lazyScenario,
|
|
1497
|
-
agentRegistryAddress: this.agentRegistryAddress,
|
|
1498
|
-
cid: pending.cid,
|
|
1499
|
-
configHash: pending.configHash,
|
|
1500
|
-
listed: true,
|
|
1501
|
-
};
|
|
1502
|
-
|
|
1503
|
-
// For scenario A, extract registration params from pending publish
|
|
1504
|
-
if (this.lazyScenario === 'A') {
|
|
1505
|
-
params.endpoint = pending.endpoint;
|
|
1506
|
-
params.serviceDescriptors = pending.serviceDescriptors;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
const calls = buildActivationBatch(params);
|
|
1510
|
-
|
|
1511
|
-
const onSuccess = () => {
|
|
1512
|
-
deletePendingPublish(this.networkId);
|
|
1513
|
-
this.lazyScenario = 'none';
|
|
1514
|
-
this.pendingPublish = null;
|
|
1515
|
-
};
|
|
1516
|
-
|
|
1517
|
-
return { calls, onSuccess };
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
/**
|
|
1521
|
-
* Non-blocking drift detection for AGIRAILS.md config.
|
|
1522
|
-
* Checks if local AGIRAILS.md matches on-chain config hash.
|
|
1523
|
-
* Logs warnings but never blocks agent operation.
|
|
1524
|
-
* @internal
|
|
1525
|
-
*/
|
|
1526
|
-
private async checkConfigDrift(config: ACTPClientConfig): Promise<void> {
|
|
1527
|
-
try {
|
|
1528
|
-
const { existsSync, readFileSync } = await import('fs');
|
|
1529
|
-
const { join } = await import('path');
|
|
1530
|
-
|
|
1531
|
-
// Look for AGIRAILS.md in cwd
|
|
1532
|
-
const agirailsMdPath = join(process.cwd(), 'AGIRAILS.md');
|
|
1533
|
-
if (!existsSync(agirailsMdPath)) {
|
|
1534
|
-
// No local file — try cached hash from pending-publish.json
|
|
1535
|
-
const { loadPendingPublish: loadPP } = await import('./config/pendingPublish');
|
|
1536
|
-
const driftNetwork = config.mode === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
1537
|
-
const pp = loadPP(driftNetwork);
|
|
1538
|
-
if (pp) {
|
|
1539
|
-
sdkLogger.info('[AGIRAILS] No AGIRAILS.md found, using cached config hash from pending-publish.json');
|
|
1540
|
-
}
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
const network = config.mode === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
1545
|
-
const networkConfig = getNetwork(network);
|
|
1546
|
-
if (!networkConfig.contracts.agentRegistry) {
|
|
1547
|
-
return; // No registry on this network
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
const content = readFileSync(agirailsMdPath, 'utf-8');
|
|
1551
|
-
const { computeConfigHash } = await import('./config/agirailsmd');
|
|
1552
|
-
const { configHash: localHash } = computeConfigHash(content);
|
|
1553
|
-
|
|
1554
|
-
const { AgentRegistryClient } = await import('./registry/AgentRegistryClient');
|
|
1555
|
-
const provider = new ethers.JsonRpcProvider(networkConfig.rpcUrl);
|
|
1556
|
-
const registryClient = AgentRegistryClient.readOnly(networkConfig.contracts.agentRegistry, provider);
|
|
1557
|
-
|
|
1558
|
-
// Detect template vs published state from frontmatter
|
|
1559
|
-
const { parseAgirailsMd: parseMd } = await import('./config/agirailsmd');
|
|
1560
|
-
const { frontmatter } = parseMd(content);
|
|
1561
|
-
const isTemplate = !frontmatter.config_hash;
|
|
1562
|
-
|
|
1563
|
-
const onChainState = await registryClient.getConfig(config.requesterAddress ?? this.info.address);
|
|
1564
|
-
const ZERO_HASH = '0x' + '0'.repeat(64);
|
|
1565
|
-
|
|
1566
|
-
if (onChainState.configHash === ZERO_HASH) {
|
|
1567
|
-
if (isTemplate) {
|
|
1568
|
-
sdkLogger.info('[AGIRAILS] AGIRAILS.md loaded (template mode). Run "actp publish" to register and sync on-chain.');
|
|
1569
|
-
} else {
|
|
1570
|
-
sdkLogger.warn('[AGIRAILS] Config not published on-chain. Run: actp publish');
|
|
1571
|
-
}
|
|
1572
|
-
} else if (onChainState.configHash !== localHash) {
|
|
1573
|
-
sdkLogger.warn('[AGIRAILS] Local AGIRAILS.md differs from on-chain. Run: actp diff');
|
|
1574
|
-
}
|
|
1575
|
-
} catch {
|
|
1576
|
-
// Silently ignore — drift detection is best-effort
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
}
|