@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.
Files changed (166) hide show
  1. package/dist/ACTPClient.d.ts +18 -0
  2. package/dist/ACTPClient.d.ts.map +1 -1
  3. package/dist/ACTPClient.js +67 -22
  4. package/dist/ACTPClient.js.map +1 -1
  5. package/dist/adapters/BasicAdapter.d.ts +12 -0
  6. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  7. package/dist/adapters/BasicAdapter.js +30 -4
  8. package/dist/adapters/BasicAdapter.js.map +1 -1
  9. package/dist/adapters/StandardAdapter.d.ts +20 -3
  10. package/dist/adapters/StandardAdapter.d.ts.map +1 -1
  11. package/dist/adapters/StandardAdapter.js +45 -11
  12. package/dist/adapters/StandardAdapter.js.map +1 -1
  13. package/dist/cli/commands/publish.js +16 -4
  14. package/dist/cli/commands/publish.js.map +1 -1
  15. package/dist/cli/commands/register.js +16 -4
  16. package/dist/cli/commands/register.js.map +1 -1
  17. package/dist/cli/commands/tx.js +31 -3
  18. package/dist/cli/commands/tx.js.map +1 -1
  19. package/dist/config/networks.d.ts +2 -2
  20. package/dist/config/networks.d.ts.map +1 -1
  21. package/dist/config/networks.js +27 -22
  22. package/dist/config/networks.js.map +1 -1
  23. package/dist/level0/request.d.ts.map +1 -1
  24. package/dist/level0/request.js +2 -1
  25. package/dist/level0/request.js.map +1 -1
  26. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  27. package/dist/runtime/BlockchainRuntime.js +11 -5
  28. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  29. package/dist/utils/IPFSClient.d.ts +3 -1
  30. package/dist/utils/IPFSClient.d.ts.map +1 -1
  31. package/dist/utils/IPFSClient.js +27 -7
  32. package/dist/utils/IPFSClient.js.map +1 -1
  33. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
  34. package/dist/wallet/AutoWalletProvider.js +52 -18
  35. package/dist/wallet/AutoWalletProvider.js.map +1 -1
  36. package/dist/wallet/SmartWalletRouter.d.ts +116 -0
  37. package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
  38. package/dist/wallet/SmartWalletRouter.js +212 -0
  39. package/dist/wallet/SmartWalletRouter.js.map +1 -0
  40. package/dist/wallet/aa/DualNonceManager.d.ts +19 -0
  41. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
  42. package/dist/wallet/aa/DualNonceManager.js +100 -5
  43. package/dist/wallet/aa/DualNonceManager.js.map +1 -1
  44. package/package.json +3 -6
  45. package/src/ACTPClient.ts +0 -1579
  46. package/src/abi/ACTPKernel.json +0 -1356
  47. package/src/abi/AgentRegistry.json +0 -915
  48. package/src/abi/ERC20.json +0 -40
  49. package/src/abi/EscrowVault.json +0 -134
  50. package/src/abi/IdentityRegistry.json +0 -316
  51. package/src/adapters/AdapterRegistry.ts +0 -173
  52. package/src/adapters/AdapterRouter.ts +0 -416
  53. package/src/adapters/BaseAdapter.ts +0 -498
  54. package/src/adapters/BasicAdapter.ts +0 -514
  55. package/src/adapters/IAdapter.ts +0 -292
  56. package/src/adapters/StandardAdapter.ts +0 -555
  57. package/src/adapters/X402Adapter.ts +0 -731
  58. package/src/adapters/index.ts +0 -60
  59. package/src/builders/DeliveryProofBuilder.ts +0 -327
  60. package/src/builders/QuoteBuilder.ts +0 -483
  61. package/src/builders/index.ts +0 -17
  62. package/src/cli/commands/balance.ts +0 -110
  63. package/src/cli/commands/batch.ts +0 -487
  64. package/src/cli/commands/config.ts +0 -231
  65. package/src/cli/commands/deploy-check.ts +0 -364
  66. package/src/cli/commands/deploy-env.ts +0 -120
  67. package/src/cli/commands/diff.ts +0 -141
  68. package/src/cli/commands/init.ts +0 -469
  69. package/src/cli/commands/mint.ts +0 -116
  70. package/src/cli/commands/pay.ts +0 -113
  71. package/src/cli/commands/publish.ts +0 -475
  72. package/src/cli/commands/pull.ts +0 -124
  73. package/src/cli/commands/register.ts +0 -247
  74. package/src/cli/commands/simulate.ts +0 -345
  75. package/src/cli/commands/time.ts +0 -302
  76. package/src/cli/commands/tx.ts +0 -448
  77. package/src/cli/commands/watch.ts +0 -211
  78. package/src/cli/index.ts +0 -134
  79. package/src/cli/utils/client.ts +0 -252
  80. package/src/cli/utils/config.ts +0 -389
  81. package/src/cli/utils/output.ts +0 -465
  82. package/src/cli/utils/wallet.ts +0 -109
  83. package/src/config/agirailsmd.ts +0 -262
  84. package/src/config/networks.ts +0 -275
  85. package/src/config/pendingPublish.ts +0 -237
  86. package/src/config/publishPipeline.ts +0 -359
  87. package/src/config/syncOperations.ts +0 -279
  88. package/src/erc8004/ERC8004Bridge.ts +0 -462
  89. package/src/erc8004/ReputationReporter.ts +0 -468
  90. package/src/erc8004/index.ts +0 -61
  91. package/src/errors/index.ts +0 -427
  92. package/src/index.ts +0 -364
  93. package/src/level0/Provider.ts +0 -117
  94. package/src/level0/ServiceDirectory.ts +0 -131
  95. package/src/level0/index.ts +0 -10
  96. package/src/level0/provide.ts +0 -132
  97. package/src/level0/request.ts +0 -432
  98. package/src/level1/Agent.ts +0 -1426
  99. package/src/level1/index.ts +0 -10
  100. package/src/level1/pricing/PriceCalculator.ts +0 -255
  101. package/src/level1/pricing/PricingStrategy.ts +0 -198
  102. package/src/level1/types/Job.ts +0 -179
  103. package/src/level1/types/Options.ts +0 -291
  104. package/src/level1/types/index.ts +0 -8
  105. package/src/protocol/ACTPKernel.ts +0 -808
  106. package/src/protocol/AgentRegistry.ts +0 -559
  107. package/src/protocol/DIDManager.ts +0 -629
  108. package/src/protocol/DIDResolver.ts +0 -554
  109. package/src/protocol/EASHelper.ts +0 -378
  110. package/src/protocol/EscrowVault.ts +0 -255
  111. package/src/protocol/EventMonitor.ts +0 -204
  112. package/src/protocol/MessageSigner.ts +0 -510
  113. package/src/protocol/ProofGenerator.ts +0 -339
  114. package/src/protocol/QuoteBuilder.ts +0 -15
  115. package/src/registry/AgentRegistryClient.ts +0 -202
  116. package/src/runtime/BlockchainRuntime.ts +0 -1015
  117. package/src/runtime/IACTPRuntime.ts +0 -306
  118. package/src/runtime/MockRuntime.ts +0 -1298
  119. package/src/runtime/MockStateManager.ts +0 -577
  120. package/src/runtime/index.ts +0 -25
  121. package/src/runtime/types/MockState.ts +0 -237
  122. package/src/storage/ArchiveBundleBuilder.ts +0 -561
  123. package/src/storage/ArweaveClient.ts +0 -946
  124. package/src/storage/FilebaseClient.ts +0 -790
  125. package/src/storage/index.ts +0 -96
  126. package/src/storage/types.ts +0 -348
  127. package/src/types/adapter.ts +0 -310
  128. package/src/types/agent.ts +0 -79
  129. package/src/types/did.ts +0 -223
  130. package/src/types/eip712.ts +0 -175
  131. package/src/types/erc8004.ts +0 -293
  132. package/src/types/escrow.ts +0 -27
  133. package/src/types/index.ts +0 -17
  134. package/src/types/message.ts +0 -145
  135. package/src/types/state.ts +0 -87
  136. package/src/types/transaction.ts +0 -69
  137. package/src/types/x402.ts +0 -251
  138. package/src/utils/ErrorRecoveryGuide.ts +0 -676
  139. package/src/utils/Helpers.ts +0 -688
  140. package/src/utils/IPFSClient.ts +0 -368
  141. package/src/utils/Logger.ts +0 -484
  142. package/src/utils/NonceManager.ts +0 -591
  143. package/src/utils/RateLimiter.ts +0 -534
  144. package/src/utils/ReceivedNonceTracker.ts +0 -567
  145. package/src/utils/SDKLifecycle.ts +0 -416
  146. package/src/utils/SecureNonce.ts +0 -78
  147. package/src/utils/Semaphore.ts +0 -276
  148. package/src/utils/UsedAttestationTracker.ts +0 -385
  149. package/src/utils/canonicalJson.ts +0 -38
  150. package/src/utils/circuitBreaker.ts +0 -324
  151. package/src/utils/computeTypeHash.ts +0 -48
  152. package/src/utils/fsSafe.ts +0 -80
  153. package/src/utils/index.ts +0 -80
  154. package/src/utils/retry.ts +0 -364
  155. package/src/utils/security.ts +0 -418
  156. package/src/utils/validation.ts +0 -540
  157. package/src/wallet/AutoWalletProvider.ts +0 -299
  158. package/src/wallet/EOAWalletProvider.ts +0 -69
  159. package/src/wallet/IWalletProvider.ts +0 -135
  160. package/src/wallet/aa/BundlerClient.ts +0 -274
  161. package/src/wallet/aa/DualNonceManager.ts +0 -173
  162. package/src/wallet/aa/PaymasterClient.ts +0 -174
  163. package/src/wallet/aa/TransactionBatcher.ts +0 -353
  164. package/src/wallet/aa/UserOpBuilder.ts +0 -246
  165. package/src/wallet/aa/constants.ts +0 -60
  166. 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
- }