@agirails/sdk 2.2.3 → 2.3.1

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 (211) hide show
  1. package/README.md +65 -31
  2. package/dist/ACTPClient.d.ts +42 -1
  3. package/dist/ACTPClient.d.ts.map +1 -1
  4. package/dist/ACTPClient.js +207 -22
  5. package/dist/ACTPClient.js.map +1 -1
  6. package/dist/abi/AgentRegistry.json +133 -0
  7. package/dist/adapters/AdapterRouter.d.ts.map +1 -1
  8. package/dist/adapters/AdapterRouter.js.map +1 -1
  9. package/dist/adapters/BasicAdapter.d.ts +10 -1
  10. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  11. package/dist/adapters/BasicAdapter.js +36 -1
  12. package/dist/adapters/BasicAdapter.js.map +1 -1
  13. package/dist/adapters/X402Adapter.d.ts +34 -7
  14. package/dist/adapters/X402Adapter.d.ts.map +1 -1
  15. package/dist/adapters/X402Adapter.js +36 -8
  16. package/dist/adapters/X402Adapter.js.map +1 -1
  17. package/dist/adapters/index.d.ts +1 -1
  18. package/dist/adapters/index.d.ts.map +1 -1
  19. package/dist/adapters/index.js.map +1 -1
  20. package/dist/cli/commands/diff.d.ts +11 -0
  21. package/dist/cli/commands/diff.d.ts.map +1 -0
  22. package/dist/cli/commands/diff.js +115 -0
  23. package/dist/cli/commands/diff.js.map +1 -0
  24. package/dist/cli/commands/init.d.ts +1 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +260 -19
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/publish.d.ts +11 -0
  29. package/dist/cli/commands/publish.d.ts.map +1 -0
  30. package/dist/cli/commands/publish.js +170 -0
  31. package/dist/cli/commands/publish.js.map +1 -0
  32. package/dist/cli/commands/pull.d.ts +12 -0
  33. package/dist/cli/commands/pull.d.ts.map +1 -0
  34. package/dist/cli/commands/pull.js +99 -0
  35. package/dist/cli/commands/pull.js.map +1 -0
  36. package/dist/cli/commands/register.d.ts +16 -0
  37. package/dist/cli/commands/register.d.ts.map +1 -0
  38. package/dist/cli/commands/register.js +211 -0
  39. package/dist/cli/commands/register.js.map +1 -0
  40. package/dist/cli/index.js +10 -0
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/cli/utils/config.d.ts +6 -0
  43. package/dist/cli/utils/config.d.ts.map +1 -1
  44. package/dist/cli/utils/config.js.map +1 -1
  45. package/dist/config/agirailsmd.d.ts +94 -0
  46. package/dist/config/agirailsmd.d.ts.map +1 -0
  47. package/dist/config/agirailsmd.js +209 -0
  48. package/dist/config/agirailsmd.js.map +1 -0
  49. package/dist/config/networks.d.ts +22 -4
  50. package/dist/config/networks.d.ts.map +1 -1
  51. package/dist/config/networks.js +64 -26
  52. package/dist/config/networks.js.map +1 -1
  53. package/dist/config/publishPipeline.d.ts +75 -0
  54. package/dist/config/publishPipeline.d.ts.map +1 -0
  55. package/dist/config/publishPipeline.js +193 -0
  56. package/dist/config/publishPipeline.js.map +1 -0
  57. package/dist/config/syncOperations.d.ts +67 -0
  58. package/dist/config/syncOperations.d.ts.map +1 -0
  59. package/dist/config/syncOperations.js +208 -0
  60. package/dist/config/syncOperations.js.map +1 -0
  61. package/dist/erc8004/ERC8004Bridge.d.ts.map +1 -1
  62. package/dist/erc8004/ERC8004Bridge.js +6 -5
  63. package/dist/erc8004/ERC8004Bridge.js.map +1 -1
  64. package/dist/erc8004/ReputationReporter.d.ts.map +1 -1
  65. package/dist/erc8004/ReputationReporter.js +9 -12
  66. package/dist/erc8004/ReputationReporter.js.map +1 -1
  67. package/dist/index.d.ts +5 -0
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +11 -3
  70. package/dist/index.js.map +1 -1
  71. package/dist/level0/request.d.ts.map +1 -1
  72. package/dist/level0/request.js +23 -86
  73. package/dist/level0/request.js.map +1 -1
  74. package/dist/level1/Agent.d.ts +0 -11
  75. package/dist/level1/Agent.d.ts.map +1 -1
  76. package/dist/level1/Agent.js +19 -36
  77. package/dist/level1/Agent.js.map +1 -1
  78. package/dist/protocol/ACTPKernel.d.ts +7 -1
  79. package/dist/protocol/ACTPKernel.d.ts.map +1 -1
  80. package/dist/protocol/ACTPKernel.js +13 -10
  81. package/dist/protocol/ACTPKernel.js.map +1 -1
  82. package/dist/protocol/EventMonitor.d.ts +14 -0
  83. package/dist/protocol/EventMonitor.d.ts.map +1 -1
  84. package/dist/protocol/EventMonitor.js +14 -0
  85. package/dist/protocol/EventMonitor.js.map +1 -1
  86. package/dist/registry/AgentRegistryClient.d.ts +75 -0
  87. package/dist/registry/AgentRegistryClient.d.ts.map +1 -0
  88. package/dist/registry/AgentRegistryClient.js +160 -0
  89. package/dist/registry/AgentRegistryClient.js.map +1 -0
  90. package/dist/runtime/BlockchainRuntime.d.ts +5 -0
  91. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  92. package/dist/runtime/BlockchainRuntime.js +1 -1
  93. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  94. package/dist/storage/ArchiveBundleBuilder.d.ts.map +1 -1
  95. package/dist/storage/ArchiveBundleBuilder.js.map +1 -1
  96. package/dist/storage/ArweaveClient.d.ts.map +1 -1
  97. package/dist/storage/ArweaveClient.js +2 -0
  98. package/dist/storage/ArweaveClient.js.map +1 -1
  99. package/dist/storage/FilebaseClient.d.ts.map +1 -1
  100. package/dist/storage/FilebaseClient.js +2 -0
  101. package/dist/storage/FilebaseClient.js.map +1 -1
  102. package/dist/types/adapter.d.ts +39 -0
  103. package/dist/types/adapter.d.ts.map +1 -1
  104. package/dist/types/adapter.js +7 -0
  105. package/dist/types/adapter.js.map +1 -1
  106. package/dist/types/x402.d.ts +23 -0
  107. package/dist/types/x402.d.ts.map +1 -1
  108. package/dist/types/x402.js.map +1 -1
  109. package/dist/utils/ErrorRecoveryGuide.d.ts.map +1 -1
  110. package/dist/utils/ErrorRecoveryGuide.js +3 -2
  111. package/dist/utils/ErrorRecoveryGuide.js.map +1 -1
  112. package/dist/utils/IPFSClient.d.ts +3 -2
  113. package/dist/utils/IPFSClient.d.ts.map +1 -1
  114. package/dist/utils/IPFSClient.js +7 -5
  115. package/dist/utils/IPFSClient.js.map +1 -1
  116. package/dist/utils/computeTypeHash.js +1 -3
  117. package/dist/utils/computeTypeHash.js.map +1 -1
  118. package/dist/utils/retry.d.ts.map +1 -1
  119. package/dist/utils/retry.js +0 -1
  120. package/dist/utils/retry.js.map +1 -1
  121. package/dist/utils/validation.d.ts +2 -2
  122. package/dist/utils/validation.d.ts.map +1 -1
  123. package/dist/utils/validation.js +2 -2
  124. package/dist/utils/validation.js.map +1 -1
  125. package/dist/wallet/AutoWalletProvider.d.ts +77 -0
  126. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -0
  127. package/dist/wallet/AutoWalletProvider.js +197 -0
  128. package/dist/wallet/AutoWalletProvider.js.map +1 -0
  129. package/dist/wallet/EOAWalletProvider.d.ts +21 -0
  130. package/dist/wallet/EOAWalletProvider.d.ts.map +1 -0
  131. package/dist/wallet/EOAWalletProvider.js +57 -0
  132. package/dist/wallet/EOAWalletProvider.js.map +1 -0
  133. package/dist/wallet/IWalletProvider.d.ts +115 -0
  134. package/dist/wallet/IWalletProvider.d.ts.map +1 -0
  135. package/dist/wallet/IWalletProvider.js +12 -0
  136. package/dist/wallet/IWalletProvider.js.map +1 -0
  137. package/dist/wallet/aa/BundlerClient.d.ts +70 -0
  138. package/dist/wallet/aa/BundlerClient.d.ts.map +1 -0
  139. package/dist/wallet/aa/BundlerClient.js +183 -0
  140. package/dist/wallet/aa/BundlerClient.js.map +1 -0
  141. package/dist/wallet/aa/DualNonceManager.d.ts +55 -0
  142. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -0
  143. package/dist/wallet/aa/DualNonceManager.js +131 -0
  144. package/dist/wallet/aa/DualNonceManager.js.map +1 -0
  145. package/dist/wallet/aa/PaymasterClient.d.ts +52 -0
  146. package/dist/wallet/aa/PaymasterClient.d.ts.map +1 -0
  147. package/dist/wallet/aa/PaymasterClient.js +115 -0
  148. package/dist/wallet/aa/PaymasterClient.js.map +1 -0
  149. package/dist/wallet/aa/TransactionBatcher.d.ts +87 -0
  150. package/dist/wallet/aa/TransactionBatcher.d.ts.map +1 -0
  151. package/dist/wallet/aa/TransactionBatcher.js +148 -0
  152. package/dist/wallet/aa/TransactionBatcher.js.map +1 -0
  153. package/dist/wallet/aa/UserOpBuilder.d.ts +71 -0
  154. package/dist/wallet/aa/UserOpBuilder.d.ts.map +1 -0
  155. package/dist/wallet/aa/UserOpBuilder.js +196 -0
  156. package/dist/wallet/aa/UserOpBuilder.js.map +1 -0
  157. package/dist/wallet/aa/constants.d.ts +54 -0
  158. package/dist/wallet/aa/constants.d.ts.map +1 -0
  159. package/dist/wallet/aa/constants.js +18 -0
  160. package/dist/wallet/aa/constants.js.map +1 -0
  161. package/dist/wallet/keystore.d.ts +16 -0
  162. package/dist/wallet/keystore.d.ts.map +1 -0
  163. package/dist/wallet/keystore.js +132 -0
  164. package/dist/wallet/keystore.js.map +1 -0
  165. package/package.json +5 -2
  166. package/src/ACTPClient.ts +275 -27
  167. package/src/abi/AgentRegistry.json +133 -0
  168. package/src/adapters/AdapterRouter.ts +0 -1
  169. package/src/adapters/BasicAdapter.ts +41 -1
  170. package/src/adapters/X402Adapter.ts +94 -16
  171. package/src/adapters/index.ts +9 -1
  172. package/src/cli/commands/diff.ts +141 -0
  173. package/src/cli/commands/init.ts +311 -22
  174. package/src/cli/commands/publish.ts +208 -0
  175. package/src/cli/commands/pull.ts +124 -0
  176. package/src/cli/commands/register.ts +233 -0
  177. package/src/cli/index.ts +12 -0
  178. package/src/cli/utils/config.ts +9 -0
  179. package/src/config/agirailsmd.ts +262 -0
  180. package/src/config/networks.ts +89 -26
  181. package/src/config/publishPipeline.ts +276 -0
  182. package/src/config/syncOperations.ts +279 -0
  183. package/src/erc8004/ERC8004Bridge.ts +6 -5
  184. package/src/erc8004/ReputationReporter.ts +14 -18
  185. package/src/index.ts +15 -0
  186. package/src/level0/request.ts +27 -88
  187. package/src/level1/Agent.ts +21 -37
  188. package/src/protocol/ACTPKernel.ts +20 -10
  189. package/src/protocol/EventMonitor.ts +14 -0
  190. package/src/registry/AgentRegistryClient.ts +202 -0
  191. package/src/runtime/BlockchainRuntime.ts +7 -1
  192. package/src/storage/ArchiveBundleBuilder.ts +0 -2
  193. package/src/storage/ArweaveClient.ts +2 -1
  194. package/src/storage/FilebaseClient.ts +3 -3
  195. package/src/types/adapter.ts +14 -0
  196. package/src/types/x402.ts +32 -0
  197. package/src/utils/ErrorRecoveryGuide.ts +4 -2
  198. package/src/utils/IPFSClient.ts +9 -7
  199. package/src/utils/computeTypeHash.ts +1 -3
  200. package/src/utils/retry.ts +0 -1
  201. package/src/utils/validation.ts +2 -2
  202. package/src/wallet/AutoWalletProvider.ts +294 -0
  203. package/src/wallet/EOAWalletProvider.ts +69 -0
  204. package/src/wallet/IWalletProvider.ts +133 -0
  205. package/src/wallet/aa/BundlerClient.ts +273 -0
  206. package/src/wallet/aa/DualNonceManager.ts +163 -0
  207. package/src/wallet/aa/PaymasterClient.ts +173 -0
  208. package/src/wallet/aa/TransactionBatcher.ts +240 -0
  209. package/src/wallet/aa/UserOpBuilder.ts +246 -0
  210. package/src/wallet/aa/constants.ts +60 -0
  211. package/src/wallet/keystore.ts +119 -0
@@ -14,6 +14,7 @@ import { NoProviderFoundError, TimeoutError, ValidationError } from '../errors';
14
14
  import { safeJSONParse, validateServiceName, isValidAddress } from '../utils/security';
15
15
  import { Logger } from '../utils/Logger';
16
16
  import { ethers } from 'ethers';
17
+ import { resolvePrivateKey } from '../wallet/keystore';
17
18
 
18
19
  /**
19
20
  * Request a service
@@ -61,7 +62,6 @@ export async function request(
61
62
  service: string,
62
63
  options: RequestOptions
63
64
  ): Promise<RequestResult> {
64
- // SECURITY FIX (H-2): Validate service name to prevent injection
65
65
  const validatedService = validateServiceName(service);
66
66
 
67
67
  const logger = new Logger({ source: 'request' });
@@ -75,12 +75,8 @@ export async function request(
75
75
  });
76
76
  }
77
77
 
78
- // SECURITY FIX (RPCURL): Use rpcUrl from options or fallback to network default
79
- // This allows Level0 request() to work with testnet/mainnet without requiring
80
- // explicit rpcUrl if user is okay with public RPC endpoints.
81
78
  let rpcUrl = options.rpcUrl;
82
79
  if (!rpcUrl && (options.network === 'testnet' || options.network === 'mainnet')) {
83
- // Import getNetwork to get default rpcUrl from network config
84
80
  const { getNetwork } = await import('../config/networks');
85
81
  const networkName = options.network === 'testnet' ? 'base-sepolia' : 'base-mainnet';
86
82
  const networkConfig = getNetwork(networkName);
@@ -88,27 +84,27 @@ export async function request(
88
84
  logger.info(`Using default RPC URL for ${networkName}: ${rpcUrl}`);
89
85
  }
90
86
 
91
- // Create ACTP client
87
+ const resolvedKey = await resolveKeyIfNeeded(options.wallet, options.network, options.stateDirectory);
88
+ const resolvedAddress = resolvedKey
89
+ ? new ethers.Wallet(resolvedKey).address.toLowerCase()
90
+ : undefined;
91
+
92
92
  const client = await ACTPClient.create({
93
93
  mode: options.network === 'testnet' ? 'testnet' : options.network === 'mainnet' ? 'mainnet' : 'mock',
94
- requesterAddress: getRequesterAddress(options.wallet),
94
+ requesterAddress: resolvedAddress || getRequesterAddress(options.wallet),
95
95
  stateDirectory: options.stateDirectory,
96
- privateKey: getPrivateKey(options.wallet),
96
+ privateKey: resolvedKey || getPrivateKey(options.wallet),
97
97
  rpcUrl,
98
98
  });
99
99
 
100
- // Calculate deadline
101
100
  const deadline = calculateDeadline(options.deadline, options.timeout);
102
-
103
- // Create transaction with service metadata
104
101
  const startTime = Date.now();
105
102
 
106
103
  try {
107
- const requesterAddress = getRequesterAddress(options.wallet);
104
+ const requesterAddress = resolvedAddress || getRequesterAddress(options.wallet);
108
105
  const amountWei = (options.budget * 1_000_000).toString(); // Convert to USDC wei (6 decimals)
109
106
 
110
107
  // In mock mode, ensure requester has enough funds
111
- // This is a convenience feature for testing - won't exist in production
112
108
  if (client.runtime && 'mintTokens' in client.runtime) {
113
109
  const mockRuntime = client.runtime as any;
114
110
  const balance = await mockRuntime.getBalance(requesterAddress);
@@ -122,30 +118,19 @@ export async function request(
122
118
  }
123
119
  }
124
120
 
125
- // ARCHITECTURE FIX: Service metadata handling
126
- // - MockRuntime: Store plaintext for provider matching and input extraction
127
- // - BlockchainRuntime: Hashes plaintext internally before sending on-chain
128
- //
129
- // Provider needs plaintext to:
130
- // 1. Match service name to handler (findServiceHandler)
131
- // 2. Extract input data for job execution (extractJobInput)
132
- //
133
- // On-chain storage uses bytes32 hash (BlockchainRuntime.validateServiceHash handles this)
134
121
  const serviceMetadata = JSON.stringify({
135
122
  service: validatedService,
136
123
  input: options.input,
137
124
  timestamp: Date.now(),
138
125
  });
139
126
 
140
- // Create transaction with structured metadata
141
- // MockRuntime stores as-is, BlockchainRuntime hashes for on-chain
142
127
  const txId = await client.runtime.createTransaction({
143
128
  provider,
144
129
  requester: requesterAddress,
145
130
  amount: amountWei,
146
131
  deadline,
147
- disputeWindow: options.disputeWindow ?? 172800, // Default 2 days
148
- serviceDescription: serviceMetadata, // Structured JSON for provider parsing
132
+ disputeWindow: options.disputeWindow ?? 172800,
133
+ serviceDescription: serviceMetadata,
149
134
  });
150
135
 
151
136
  // Call onProgress if provided
@@ -187,12 +172,8 @@ export async function request(
187
172
  attempts++;
188
173
  }
189
174
 
190
- // Check if we got a result
191
175
  if (!tx || (tx.state !== 'DELIVERED' && tx.state !== 'SETTLED')) {
192
- const _timedOut = true; // Flag for potential future use
193
-
194
- // SECURITY FIX (H-3): Auto-cancel transaction on timeout if still in early state
195
- // This prevents funds from being locked indefinitely if provider never responds
176
+ // Auto-cancel on timeout if still in early state
196
177
  if (tx && (tx.state === 'INITIATED' || tx.state === 'COMMITTED')) {
197
178
  try {
198
179
  logger.warn('Transaction timed out, cancelling to release funds', {
@@ -200,8 +181,6 @@ export async function request(
200
181
  state: tx.state,
201
182
  });
202
183
 
203
- // ACTUALLY CANCEL THE TRANSACTION
204
- // Check if runtime has cancelTransaction method
205
184
  if ('cancelTransaction' in client.runtime) {
206
185
  await (client.runtime as any).cancelTransaction(txId);
207
186
  logger.info('Transaction cancelled successfully', { txId });
@@ -211,7 +190,6 @@ export async function request(
211
190
  (error as any).wasCancelled = true;
212
191
  throw error;
213
192
  } else {
214
- // Fallback: Transition to CANCELLED state
215
193
  await client.runtime.transitionState(txId, 'CANCELLED');
216
194
  logger.info('Transaction cancelled successfully (via transitionState)', { txId });
217
195
 
@@ -222,7 +200,6 @@ export async function request(
222
200
  }
223
201
  } catch (cancelError) {
224
202
  logger.error('Failed to cancel timed-out transaction', { txId }, cancelError as Error);
225
- // Continue with original timeout error
226
203
  }
227
204
  }
228
205
 
@@ -232,13 +209,8 @@ export async function request(
232
209
  throw error;
233
210
  }
234
211
 
235
- // SECURITY FIX (C-3): Safe JSON parsing with schema validation
236
- // Extract result from delivery proof
237
212
  let deliveredResult: any = {};
238
213
  if (tx.deliveryProof) {
239
- // Define expected schema for delivery proof
240
- // NOTE: 'type' field with value 'delivery.proof' is the unique wrapper marker
241
- // (set by ProofGenerator.generateDeliveryProof) that handlers won't naturally return
242
214
  const DELIVERY_PROOF_SCHEMA: Record<string, string> = {
243
215
  result: 'any',
244
216
  data: 'any',
@@ -247,28 +219,18 @@ export async function request(
247
219
  timestamp: 'number',
248
220
  contentHash: 'string',
249
221
  txId: 'string',
250
- type: 'string', // Unique marker: 'delivery.proof'
222
+ type: 'string',
251
223
  };
252
224
 
253
- // Use safeJSONParse with schema validation which:
254
- // 1. Validates JSON structure
255
- // 2. Removes __proto__, constructor, prototype properties
256
- // 3. Prevents prototype pollution attacks
257
- // 4. Validates against expected schema
258
- // 5. Checks size limits to prevent DoS
259
- // 6. Returns null if parsing fails
260
225
  const parsed = safeJSONParse(tx.deliveryProof, DELIVERY_PROOF_SCHEMA);
261
226
 
262
227
  if (parsed !== null) {
263
228
  deliveredResult = parsed;
264
229
  } else {
265
- // If parsing failed, treat as plain text (but don't execute or eval)
266
230
  deliveredResult = { data: tx.deliveryProof };
267
231
  logger.warn('Failed to parse delivery proof as JSON', { txId });
268
232
  }
269
233
  } else if (options.network === 'testnet' || options.network === 'mainnet') {
270
- // KNOWN LIMITATION: BlockchainRuntime doesn't fetch deliveryProof from IPFS yet
271
- // Result will be empty until this is implemented
272
234
  logger.warn(
273
235
  'Delivery proof retrieval not yet implemented for testnet/mainnet. ' +
274
236
  'Result may be empty. Use ACTPClient with manual proof handling for production.',
@@ -276,17 +238,11 @@ export async function request(
276
238
  );
277
239
  }
278
240
 
279
- // SECURITY FIX (CRITICAL-2): Release escrow only after proper validation
280
- // For mock mode, auto-release is safe. For testnet/mainnet, require attestation.
281
241
  if (tx.state === 'DELIVERED' && tx.escrowId) {
282
- // Wait for dispute window to expire
283
242
  const disputeWindowEnd = (tx.completedAt ?? 0) + tx.disputeWindow;
284
243
  const currentTime = client.runtime.time.now();
285
244
 
286
245
  if (currentTime >= disputeWindowEnd) {
287
- // SECURITY FIX (CRITICAL-2): Only auto-release in mock mode
288
- // For real networks, the requester should manually verify and release
289
- // or use attestation-based verification
290
246
  const isMockMode = options.network !== 'testnet' && options.network !== 'mainnet';
291
247
 
292
248
  if (isMockMode) {
@@ -353,6 +309,20 @@ export async function request(
353
309
  }
354
310
  }
355
311
 
312
+ /**
313
+ * Resolve private key from keystore if wallet is auto/undefined and network is testnet/mainnet.
314
+ * Returns undefined if wallet is explicitly set (caller should use getPrivateKey instead).
315
+ */
316
+ async function resolveKeyIfNeeded(
317
+ wallet?: 'auto' | 'connect' | string | { privateKey: string },
318
+ network?: string,
319
+ stateDirectory?: string
320
+ ): Promise<string | undefined> {
321
+ if (wallet && wallet !== 'auto') return undefined; // explicit wallet, skip auto-detect
322
+ if (network !== 'testnet' && network !== 'mainnet') return undefined;
323
+ return resolvePrivateKey(stateDirectory);
324
+ }
325
+
356
326
  /**
357
327
  * Find provider for service
358
328
  *
@@ -381,23 +351,10 @@ function findProvider(
381
351
  return providers[0];
382
352
  }
383
353
 
384
- /**
385
- * Get requester address from wallet option
386
- *
387
- * SECURITY FIX (HIGH): Properly derive addresses from private keys using ethers
388
- * Never fabricate addresses or use partial key slices as addresses.
389
- *
390
- * @param wallet - Wallet configuration
391
- * @returns Ethereum address
392
- * @throws {ValidationError} If address format is invalid
393
- */
394
354
  function getRequesterAddress(
395
355
  wallet?: 'auto' | 'connect' | string | { privateKey: string }
396
356
  ): string {
397
- // For mock mode only: generate deterministic address
398
- // This is only safe because mock mode doesn't involve real funds
399
357
  if (!wallet || wallet === 'auto') {
400
- // Create a valid Ethereum address (40 hex chars) - ONLY for mock mode
401
358
  const hex = Buffer.from('requester').toString('hex');
402
359
  return '0x' + hex.padEnd(40, '0');
403
360
  }
@@ -407,15 +364,12 @@ function getRequesterAddress(
407
364
  }
408
365
 
409
366
  if (typeof wallet === 'string') {
410
- // SECURITY FIX (HIGH): Validate address format
411
367
  if (!isValidAddress(wallet)) {
412
368
  throw new ValidationError('wallet', `Invalid Ethereum address format: ${wallet}`);
413
369
  }
414
370
  return wallet.toLowerCase();
415
371
  }
416
372
 
417
- // SECURITY FIX (HIGH): Derive address from private key using ethers
418
- // This is the correct way to get address from a private key
419
373
  try {
420
374
  const walletInstance = new ethers.Wallet(wallet.privateKey);
421
375
  return walletInstance.address.toLowerCase();
@@ -424,15 +378,6 @@ function getRequesterAddress(
424
378
  }
425
379
  }
426
380
 
427
- /**
428
- * Get private key from wallet option
429
- *
430
- * SECURITY FIX (HIGH): Validate private key format before use
431
- *
432
- * @param wallet - Wallet configuration
433
- * @returns Private key or undefined
434
- * @throws {ValidationError} If private key format is invalid
435
- */
436
381
  function getPrivateKey(
437
382
  wallet?: 'auto' | 'connect' | string | { privateKey: string }
438
383
  ): string | undefined {
@@ -440,12 +385,8 @@ function getPrivateKey(
440
385
  return undefined;
441
386
  }
442
387
 
443
- // If wallet is a string that looks like a private key (0x + 64 hex chars), use it
444
- // Otherwise treat it as an address and return undefined
445
388
  if (typeof wallet === 'string') {
446
- // Check if it looks like a private key (0x + 64 hex chars)
447
389
  if (/^0x[0-9a-fA-F]{64}$/.test(wallet)) {
448
- // Validate by trying to create a wallet
449
390
  try {
450
391
  new ethers.Wallet(wallet);
451
392
  return wallet;
@@ -453,11 +394,9 @@ function getPrivateKey(
453
394
  throw new ValidationError('wallet', 'Invalid private key format');
454
395
  }
455
396
  }
456
- // It's an address, not a private key
457
397
  return undefined;
458
398
  }
459
399
 
460
- // Validate private key format
461
400
  if (wallet.privateKey) {
462
401
  try {
463
402
  new ethers.Wallet(wallet.privateKey);
@@ -13,12 +13,13 @@ import * as os from 'os';
13
13
  import * as fs from 'fs';
14
14
  import { ethers } from 'ethers';
15
15
  import { ACTPClient } from '../ACTPClient';
16
+ import { resolvePrivateKey } from '../wallet/keystore';
16
17
  import { Job, JobHandler, JobContext } from './types/Job';
17
18
  import { RequestOptions, RequestResult, NetworkOption } from './types/Options';
18
19
  import { PricingStrategy } from './pricing/PricingStrategy';
19
20
  import { AgentLifecycleError, ServiceConfigError, ValidationError } from '../errors';
20
21
  import { validateServiceName, validatePath, LRUCache } from '../utils/security';
21
- import { Logger } from '../utils/Logger';
22
+ import { Logger, sdkLogger } from '../utils/Logger';
22
23
  import { ServiceHash } from '../utils/Helpers';
23
24
  import { Semaphore } from '../utils/Semaphore';
24
25
  import { ProofGenerator } from '../protocol/ProofGenerator';
@@ -386,12 +387,8 @@ export class Agent extends EventEmitter {
386
387
  this.emit('starting');
387
388
 
388
389
  try {
389
- // SECURITY FIX (RPCURL): Use rpcUrl from config or fallback to network default
390
- // This allows Agent to work with testnet/mainnet without requiring explicit rpcUrl
391
- // if user is okay with public RPC endpoints.
392
390
  let rpcUrl = this.config.rpcUrl;
393
391
  if (!rpcUrl && (this.network === 'testnet' || this.network === 'mainnet')) {
394
- // Import getNetwork to get default rpcUrl from network config
395
392
  const { getNetwork } = await import('../config/networks');
396
393
  const networkName = this.network === 'testnet' ? 'base-sepolia' : 'base-mainnet';
397
394
  const networkConfig = getNetwork(networkName);
@@ -399,16 +396,14 @@ export class Agent extends EventEmitter {
399
396
  this.logger.info(`Using default RPC URL for ${networkName}: ${rpcUrl}`);
400
397
  }
401
398
 
402
- // Initialize ACTP client
403
399
  this._client = await ACTPClient.create({
404
400
  mode: this.network === 'testnet' ? 'testnet' : this.network === 'mainnet' ? 'mainnet' : 'mock',
405
- requesterAddress: this.address || this.generateAddress(),
401
+ requesterAddress: this.address || await this.generateAddress(),
406
402
  stateDirectory: this.config.stateDirectory,
407
- privateKey: this.getPrivateKey(),
403
+ privateKey: await this.getPrivateKey(),
408
404
  rpcUrl,
409
405
  });
410
406
 
411
- // Start polling for jobs
412
407
  this.startPolling();
413
408
 
414
409
  this._status = 'running';
@@ -1316,21 +1311,21 @@ export class Agent extends EventEmitter {
1316
1311
  log: {
1317
1312
  debug: (message: string, meta?: any) => {
1318
1313
  if (agent.config.logging?.level === 'debug') {
1319
- console.debug(`[${job.id}] ${message}`, meta);
1314
+ sdkLogger.debug(`[${job.id}] ${message}`, meta);
1320
1315
  }
1321
1316
  },
1322
1317
  info: (message: string, meta?: any) => {
1323
1318
  if (['debug', 'info'].includes(agent.config.logging?.level || 'info')) {
1324
- console.info(`[${job.id}] ${message}`, meta);
1319
+ sdkLogger.info(`[${job.id}] ${message}`, meta);
1325
1320
  }
1326
1321
  },
1327
1322
  warn: (message: string, meta?: any) => {
1328
1323
  if (['debug', 'info', 'warn'].includes(agent.config.logging?.level || 'info')) {
1329
- console.warn(`[${job.id}] ${message}`, meta);
1324
+ sdkLogger.warn(`[${job.id}] ${message}`, meta);
1330
1325
  }
1331
1326
  },
1332
1327
  error: (message: string, meta?: any) => {
1333
- console.error(`[${job.id}] ${message}`, meta);
1328
+ sdkLogger.error(`[${job.id}] ${message}`, meta);
1334
1329
  },
1335
1330
  },
1336
1331
 
@@ -1371,15 +1366,8 @@ export class Agent extends EventEmitter {
1371
1366
  }
1372
1367
  }
1373
1368
 
1374
- /**
1375
- * Generate address based on wallet configuration
1376
- *
1377
- * SECURITY FIX (HIGH): For testnet/mainnet, MUST derive from private key.
1378
- * For mock mode, can use deterministic address for convenience.
1379
- */
1380
- private generateAddress(): string {
1381
- // If wallet has private key, ALWAYS derive address from it
1382
- const privateKey = this.getPrivateKey();
1369
+ private async generateAddress(): Promise<string> {
1370
+ const privateKey = await this.getPrivateKey();
1383
1371
  if (privateKey) {
1384
1372
  try {
1385
1373
  const wallet = new ethers.Wallet(privateKey);
@@ -1389,33 +1377,31 @@ export class Agent extends EventEmitter {
1389
1377
  }
1390
1378
  }
1391
1379
 
1392
- // For non-mock networks, require a valid private key or address
1393
1380
  if (this.network === 'testnet' || this.network === 'mainnet') {
1394
1381
  throw new ValidationError(
1395
1382
  'wallet',
1396
- `${this.network} mode requires a valid private key or address in wallet configuration`
1383
+ `${this.network} mode requires a valid private key or address in wallet configuration.\n` +
1384
+ 'Run "actp init" to generate a keystore, or set ACTP_PRIVATE_KEY env var.'
1397
1385
  );
1398
1386
  }
1399
1387
 
1400
- // For mock mode only: generate deterministic address from agent name
1401
- // This is safe because mock mode doesn't involve real funds
1402
1388
  return `0x${Buffer.from(this.name).toString('hex').padEnd(40, '0').slice(0, 40)}`;
1403
1389
  }
1404
1390
 
1405
- /**
1406
- * Get private key from configuration
1407
- *
1408
- * SECURITY FIX (HIGH): Validate private key format before use
1409
- */
1410
- private getPrivateKey(): string | undefined {
1411
- if (!this.config.wallet || this.config.wallet === 'auto' || this.config.wallet === 'connect') {
1391
+ private async getPrivateKey(): Promise<string | undefined> {
1392
+ if (!this.config.wallet || this.config.wallet === 'auto') {
1393
+ if (this.network === 'testnet' || this.network === 'mainnet') {
1394
+ return resolvePrivateKey(this.config.stateDirectory);
1395
+ }
1396
+ return undefined;
1397
+ }
1398
+
1399
+ if (this.config.wallet === 'connect') {
1412
1400
  return undefined;
1413
1401
  }
1414
1402
 
1415
1403
  if (typeof this.config.wallet === 'string') {
1416
- // Check if it looks like a private key (0x + 64 hex chars)
1417
1404
  if (/^0x[0-9a-fA-F]{64}$/.test(this.config.wallet)) {
1418
- // Validate by trying to create a wallet
1419
1405
  try {
1420
1406
  new ethers.Wallet(this.config.wallet);
1421
1407
  return this.config.wallet;
@@ -1423,11 +1409,9 @@ export class Agent extends EventEmitter {
1423
1409
  throw new ValidationError('wallet', 'Invalid private key format');
1424
1410
  }
1425
1411
  }
1426
- // It's an address, not a private key
1427
1412
  return undefined;
1428
1413
  }
1429
1414
 
1430
- // Validate private key format
1431
1415
  if (this.config.wallet.privateKey) {
1432
1416
  try {
1433
1417
  new ethers.Wallet(this.config.wallet.privateKey);
@@ -37,14 +37,25 @@ interface GasOptions {
37
37
  export class ACTPKernel {
38
38
  private contract: Contract;
39
39
  private readonly gasSettings?: GasOptions;
40
+ /**
41
+ * Number of block confirmations to wait after each state-changing tx.
42
+ * Default: 2 (Base L2 reorg safety — ~4-6 s on Base's 2 s blocks).
43
+ * Set to 1 for faster feedback on testnet; never set to 0 in production.
44
+ */
45
+ private readonly confirmations: number;
40
46
 
41
47
  constructor(
42
48
  private readonly address: string,
43
49
  signer: Signer,
44
- gasSettings?: GasOptions
50
+ gasSettings?: GasOptions,
51
+ confirmations: number = 2
45
52
  ) {
53
+ if (confirmations < 1) {
54
+ throw new Error(`confirmations must be >= 1, got ${confirmations}`);
55
+ }
46
56
  this.contract = new Contract(address, ACTPKernelABI, signer);
47
57
  this.gasSettings = gasSettings;
58
+ this.confirmations = confirmations;
48
59
  }
49
60
 
50
61
  /**
@@ -213,7 +224,7 @@ export class ACTPKernel {
213
224
  txOptions
214
225
  );
215
226
 
216
- const receipt = await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
227
+ const receipt = await tx.wait(this.confirmations);
217
228
  if (!receipt) {
218
229
  throw new Error('Transaction receipt not available');
219
230
  }
@@ -280,7 +291,7 @@ export class ACTPKernel {
280
291
 
281
292
  const tx = await transitionFunc(txId, newState, proof, txOptions);
282
293
 
283
- await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
294
+ await tx.wait(this.confirmations);
284
295
  } catch (error: any) {
285
296
  throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
286
297
  }
@@ -400,8 +411,7 @@ export class ACTPKernel {
400
411
 
401
412
  const tx = await linkEscrowFunc(txId, escrowContract, escrowId, txOptions);
402
413
 
403
- // Wait for 2 confirmations to ensure state is updated on RPC nodes (Base Sepolia reorg safety)
404
- await tx.wait(2);
414
+ await tx.wait(this.confirmations);
405
415
  } catch (error: any) {
406
416
  throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
407
417
  }
@@ -437,7 +447,7 @@ export class ACTPKernel {
437
447
 
438
448
  const tx = await releaseMilestoneFunc(txId, amount, txOptions);
439
449
 
440
- await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
450
+ await tx.wait(this.confirmations);
441
451
  } catch (error: any) {
442
452
  throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
443
453
  }
@@ -521,7 +531,7 @@ export class ACTPKernel {
521
531
 
522
532
  const tx = await releaseEscrowFunc(txId, txOptions);
523
533
 
524
- await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
534
+ await tx.wait(this.confirmations);
525
535
  } catch (error: any) {
526
536
  throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
527
537
  }
@@ -669,7 +679,7 @@ export class ACTPKernel {
669
679
  txOptions
670
680
  );
671
681
 
672
- await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
682
+ await tx.wait(this.confirmations);
673
683
  } catch (error: any) {
674
684
  throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
675
685
  }
@@ -735,7 +745,7 @@ export class ACTPKernel {
735
745
  txOptions
736
746
  );
737
747
 
738
- await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
748
+ await tx.wait(this.confirmations);
739
749
  } catch (error: any) {
740
750
  throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
741
751
  }
@@ -789,7 +799,7 @@ export class ACTPKernel {
789
799
  txOptions
790
800
  );
791
801
 
792
- await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
802
+ await tx.wait(this.confirmations);
793
803
  } catch (error: any) {
794
804
  throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
795
805
  }
@@ -4,6 +4,20 @@ import { State, Transaction } from '../types';
4
4
  /**
5
5
  * EventMonitor - Listen to blockchain events
6
6
  *
7
+ * ## Confirmation Policy
8
+ *
9
+ * Events received by EventMonitor are already confirmed. ACTPKernel waits
10
+ * for N block confirmations (default 2, configurable via `confirmations`
11
+ * parameter in BlockchainRuntimeConfig) before returning from state-changing
12
+ * operations. On Base L2 (~2 s blocks), the default means events arrive
13
+ * ~4-6 s after submission and are safe from reorgs.
14
+ *
15
+ * Confirmation flow:
16
+ * User calls ACTPKernel.createTransaction()
17
+ * → tx.wait(confirmations) blocks until N confirmations
18
+ * → Event emitted (already confirmed)
19
+ * → EventMonitor receives event (instant)
20
+ *
7
21
  * SECURITY FIX (EVENT-MONITOR): Corrected event parameter order to match ABI.
8
22
  * Per ACTPKernel.json, TransactionCreated signature is:
9
23
  * (bytes32 indexed transactionId, address indexed requester, address indexed provider, uint256 amount, bytes32 serviceHash)