@agirails/sdk 2.0.1-beta → 2.0.2

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 (405) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +116 -108
  3. package/bin/actp +10 -0
  4. package/dist/ACTPClient.d.ts +456 -33
  5. package/dist/ACTPClient.d.ts.map +1 -1
  6. package/dist/ACTPClient.js +477 -93
  7. package/dist/ACTPClient.js.map +1 -1
  8. package/dist/abi/AgentRegistry.json +782 -0
  9. package/dist/abi/EscrowVault.json +106 -38
  10. package/dist/abi/IdentityRegistry.json +316 -0
  11. package/dist/adapters/BaseAdapter.d.ts +231 -0
  12. package/dist/adapters/BaseAdapter.d.ts.map +1 -0
  13. package/dist/adapters/BaseAdapter.js +393 -0
  14. package/dist/adapters/BaseAdapter.js.map +1 -0
  15. package/dist/adapters/BeginnerAdapter.d.ts +152 -0
  16. package/dist/adapters/BeginnerAdapter.d.ts.map +1 -0
  17. package/dist/adapters/BeginnerAdapter.js +168 -0
  18. package/dist/adapters/BeginnerAdapter.js.map +1 -0
  19. package/dist/adapters/IntermediateAdapter.d.ts +211 -0
  20. package/dist/adapters/IntermediateAdapter.d.ts.map +1 -0
  21. package/dist/adapters/IntermediateAdapter.js +260 -0
  22. package/dist/adapters/IntermediateAdapter.js.map +1 -0
  23. package/dist/adapters/index.d.ts +15 -0
  24. package/dist/adapters/index.d.ts.map +1 -0
  25. package/dist/adapters/index.js +26 -0
  26. package/dist/adapters/index.js.map +1 -0
  27. package/dist/builders/DeliveryProofBuilder.d.ts +60 -1
  28. package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -1
  29. package/dist/builders/DeliveryProofBuilder.js +81 -5
  30. package/dist/builders/DeliveryProofBuilder.js.map +1 -1
  31. package/dist/builders/QuoteBuilder.d.ts +101 -0
  32. package/dist/builders/QuoteBuilder.d.ts.map +1 -1
  33. package/dist/builders/QuoteBuilder.js +120 -3
  34. package/dist/builders/QuoteBuilder.js.map +1 -1
  35. package/dist/builders/index.d.ts +4 -0
  36. package/dist/builders/index.d.ts.map +1 -1
  37. package/dist/builders/index.js +4 -0
  38. package/dist/builders/index.js.map +1 -1
  39. package/dist/cli/commands/balance.d.ts +13 -0
  40. package/dist/cli/commands/balance.d.ts.map +1 -0
  41. package/dist/cli/commands/balance.js +89 -0
  42. package/dist/cli/commands/balance.js.map +1 -0
  43. package/dist/cli/commands/batch.d.ts +24 -0
  44. package/dist/cli/commands/batch.d.ts.map +1 -0
  45. package/dist/cli/commands/batch.js +424 -0
  46. package/dist/cli/commands/batch.js.map +1 -0
  47. package/dist/cli/commands/config.d.ts +13 -0
  48. package/dist/cli/commands/config.d.ts.map +1 -0
  49. package/dist/cli/commands/config.js +192 -0
  50. package/dist/cli/commands/config.js.map +1 -0
  51. package/dist/cli/commands/init.d.ts +19 -0
  52. package/dist/cli/commands/init.d.ts.map +1 -0
  53. package/dist/cli/commands/init.js +143 -0
  54. package/dist/cli/commands/init.js.map +1 -0
  55. package/dist/cli/commands/mint.d.ts +13 -0
  56. package/dist/cli/commands/mint.d.ts.map +1 -0
  57. package/dist/cli/commands/mint.js +91 -0
  58. package/dist/cli/commands/mint.js.map +1 -0
  59. package/dist/cli/commands/pay.d.ts +18 -0
  60. package/dist/cli/commands/pay.d.ts.map +1 -0
  61. package/dist/cli/commands/pay.js +87 -0
  62. package/dist/cli/commands/pay.js.map +1 -0
  63. package/dist/cli/commands/simulate.d.ts +32 -0
  64. package/dist/cli/commands/simulate.d.ts.map +1 -0
  65. package/dist/cli/commands/simulate.js +290 -0
  66. package/dist/cli/commands/simulate.js.map +1 -0
  67. package/dist/cli/commands/time.d.ts +29 -0
  68. package/dist/cli/commands/time.d.ts.map +1 -0
  69. package/dist/cli/commands/time.js +252 -0
  70. package/dist/cli/commands/time.js.map +1 -0
  71. package/dist/cli/commands/tx.d.ts +16 -0
  72. package/dist/cli/commands/tx.d.ts.map +1 -0
  73. package/dist/cli/commands/tx.js +379 -0
  74. package/dist/cli/commands/tx.js.map +1 -0
  75. package/dist/cli/commands/watch.d.ts +20 -0
  76. package/dist/cli/commands/watch.d.ts.map +1 -0
  77. package/dist/cli/commands/watch.js +160 -0
  78. package/dist/cli/commands/watch.js.map +1 -0
  79. package/dist/cli/index.d.ts +17 -0
  80. package/dist/cli/index.d.ts.map +1 -0
  81. package/dist/cli/index.js +104 -0
  82. package/dist/cli/index.js.map +1 -0
  83. package/dist/cli/utils/client.d.ts +70 -0
  84. package/dist/cli/utils/client.d.ts.map +1 -0
  85. package/dist/cli/utils/client.js +240 -0
  86. package/dist/cli/utils/client.js.map +1 -0
  87. package/dist/cli/utils/config.d.ts +91 -0
  88. package/dist/cli/utils/config.d.ts.map +1 -0
  89. package/dist/cli/utils/config.js +240 -0
  90. package/dist/cli/utils/config.js.map +1 -0
  91. package/dist/cli/utils/output.d.ts +174 -0
  92. package/dist/cli/utils/output.d.ts.map +1 -0
  93. package/dist/cli/utils/output.js +380 -0
  94. package/dist/cli/utils/output.js.map +1 -0
  95. package/dist/config/networks.d.ts +28 -0
  96. package/dist/config/networks.d.ts.map +1 -1
  97. package/dist/config/networks.js +60 -12
  98. package/dist/config/networks.js.map +1 -1
  99. package/dist/errors/index.d.ts +165 -2
  100. package/dist/errors/index.d.ts.map +1 -1
  101. package/dist/errors/index.js +260 -2
  102. package/dist/errors/index.js.map +1 -1
  103. package/dist/index.d.ts +61 -13
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +141 -36
  106. package/dist/index.js.map +1 -1
  107. package/dist/level0/Provider.d.ts +106 -0
  108. package/dist/level0/Provider.d.ts.map +1 -0
  109. package/dist/level0/Provider.js +10 -0
  110. package/dist/level0/Provider.js.map +1 -0
  111. package/dist/level0/ServiceDirectory.d.ts +74 -0
  112. package/dist/level0/ServiceDirectory.d.ts.map +1 -0
  113. package/dist/level0/ServiceDirectory.js +122 -0
  114. package/dist/level0/ServiceDirectory.js.map +1 -0
  115. package/dist/level0/index.d.ts +10 -0
  116. package/dist/level0/index.d.ts.map +1 -0
  117. package/dist/level0/index.js +15 -0
  118. package/dist/level0/index.js.map +1 -0
  119. package/dist/level0/provide.d.ts +51 -0
  120. package/dist/level0/provide.d.ts.map +1 -0
  121. package/dist/level0/provide.js +113 -0
  122. package/dist/level0/provide.js.map +1 -0
  123. package/dist/level0/request.d.ts +53 -0
  124. package/dist/level0/request.d.ts.map +1 -0
  125. package/dist/level0/request.js +462 -0
  126. package/dist/level0/request.js.map +1 -0
  127. package/dist/level1/Agent.d.ts +472 -0
  128. package/dist/level1/Agent.d.ts.map +1 -0
  129. package/dist/level1/Agent.js +1091 -0
  130. package/dist/level1/Agent.js.map +1 -0
  131. package/dist/level1/index.d.ts +10 -0
  132. package/dist/level1/index.d.ts.map +1 -0
  133. package/dist/level1/index.js +30 -0
  134. package/dist/level1/index.js.map +1 -0
  135. package/dist/level1/pricing/PriceCalculator.d.ts +62 -0
  136. package/dist/level1/pricing/PriceCalculator.d.ts.map +1 -0
  137. package/dist/level1/pricing/PriceCalculator.js +237 -0
  138. package/dist/level1/pricing/PriceCalculator.js.map +1 -0
  139. package/dist/level1/pricing/PricingStrategy.d.ts +179 -0
  140. package/dist/level1/pricing/PricingStrategy.d.ts.map +1 -0
  141. package/dist/level1/pricing/PricingStrategy.js +11 -0
  142. package/dist/level1/pricing/PricingStrategy.js.map +1 -0
  143. package/dist/level1/types/Job.d.ts +166 -0
  144. package/dist/level1/types/Job.d.ts.map +1 -0
  145. package/dist/level1/types/Job.js +11 -0
  146. package/dist/level1/types/Job.js.map +1 -0
  147. package/dist/level1/types/Options.d.ts +258 -0
  148. package/dist/level1/types/Options.d.ts.map +1 -0
  149. package/dist/level1/types/Options.js +8 -0
  150. package/dist/level1/types/Options.js.map +1 -0
  151. package/dist/level1/types/index.d.ts +8 -0
  152. package/dist/level1/types/index.d.ts.map +1 -0
  153. package/dist/level1/types/index.js +8 -0
  154. package/dist/level1/types/index.js.map +1 -0
  155. package/dist/protocol/ACTPKernel.d.ts +229 -2
  156. package/dist/protocol/ACTPKernel.d.ts.map +1 -1
  157. package/dist/protocol/ACTPKernel.js +367 -33
  158. package/dist/protocol/ACTPKernel.js.map +1 -1
  159. package/dist/protocol/AgentRegistry.d.ts +177 -0
  160. package/dist/protocol/AgentRegistry.d.ts.map +1 -0
  161. package/dist/protocol/AgentRegistry.js +449 -0
  162. package/dist/protocol/AgentRegistry.js.map +1 -0
  163. package/dist/protocol/DIDManager.d.ts +289 -0
  164. package/dist/protocol/DIDManager.d.ts.map +1 -0
  165. package/dist/protocol/DIDManager.js +481 -0
  166. package/dist/protocol/DIDManager.js.map +1 -0
  167. package/dist/protocol/DIDResolver.d.ts +236 -0
  168. package/dist/protocol/DIDResolver.d.ts.map +1 -0
  169. package/dist/protocol/DIDResolver.js +495 -0
  170. package/dist/protocol/DIDResolver.js.map +1 -0
  171. package/dist/protocol/EASHelper.d.ts +57 -2
  172. package/dist/protocol/EASHelper.d.ts.map +1 -1
  173. package/dist/protocol/EASHelper.js +230 -37
  174. package/dist/protocol/EASHelper.js.map +1 -1
  175. package/dist/protocol/EscrowVault.d.ts +93 -2
  176. package/dist/protocol/EscrowVault.d.ts.map +1 -1
  177. package/dist/protocol/EscrowVault.js +122 -33
  178. package/dist/protocol/EscrowVault.js.map +1 -1
  179. package/dist/protocol/EventMonitor.d.ts +45 -1
  180. package/dist/protocol/EventMonitor.d.ts.map +1 -1
  181. package/dist/protocol/EventMonitor.js +64 -8
  182. package/dist/protocol/EventMonitor.js.map +1 -1
  183. package/dist/protocol/MessageSigner.d.ts +116 -2
  184. package/dist/protocol/MessageSigner.d.ts.map +1 -1
  185. package/dist/protocol/MessageSigner.js +215 -9
  186. package/dist/protocol/MessageSigner.js.map +1 -1
  187. package/dist/protocol/ProofGenerator.d.ts +93 -0
  188. package/dist/protocol/ProofGenerator.d.ts.map +1 -1
  189. package/dist/protocol/ProofGenerator.js +194 -9
  190. package/dist/protocol/ProofGenerator.js.map +1 -1
  191. package/dist/protocol/QuoteBuilder.d.ts +8 -0
  192. package/dist/protocol/QuoteBuilder.d.ts.map +1 -1
  193. package/dist/protocol/QuoteBuilder.js +8 -0
  194. package/dist/protocol/QuoteBuilder.js.map +1 -1
  195. package/dist/runtime/BlockchainRuntime.d.ts +360 -0
  196. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -0
  197. package/dist/runtime/BlockchainRuntime.js +767 -0
  198. package/dist/runtime/BlockchainRuntime.js.map +1 -0
  199. package/dist/runtime/IACTPRuntime.d.ts +271 -0
  200. package/dist/runtime/IACTPRuntime.d.ts.map +1 -0
  201. package/dist/runtime/IACTPRuntime.js +15 -0
  202. package/dist/runtime/IACTPRuntime.js.map +1 -0
  203. package/dist/runtime/MockRuntime.d.ts +445 -0
  204. package/dist/runtime/MockRuntime.d.ts.map +1 -0
  205. package/dist/runtime/MockRuntime.js +1065 -0
  206. package/dist/runtime/MockRuntime.js.map +1 -0
  207. package/dist/runtime/MockStateManager.d.ts +233 -0
  208. package/dist/runtime/MockStateManager.d.ts.map +1 -0
  209. package/dist/runtime/MockStateManager.js +533 -0
  210. package/dist/runtime/MockStateManager.js.map +1 -0
  211. package/dist/runtime/index.d.ts +14 -0
  212. package/dist/runtime/index.d.ts.map +1 -0
  213. package/dist/runtime/index.js +42 -0
  214. package/dist/runtime/index.js.map +1 -0
  215. package/dist/runtime/types/MockState.d.ts +167 -0
  216. package/dist/runtime/types/MockState.d.ts.map +1 -0
  217. package/dist/runtime/types/MockState.js +43 -0
  218. package/dist/runtime/types/MockState.js.map +1 -0
  219. package/dist/types/agent.d.ts +76 -0
  220. package/dist/types/agent.d.ts.map +1 -0
  221. package/dist/types/agent.js +8 -0
  222. package/dist/types/agent.js.map +1 -0
  223. package/dist/types/did.d.ts +192 -0
  224. package/dist/types/did.d.ts.map +1 -0
  225. package/dist/types/did.js +38 -0
  226. package/dist/types/did.js.map +1 -0
  227. package/dist/types/eip712.d.ts +34 -0
  228. package/dist/types/eip712.d.ts.map +1 -1
  229. package/dist/types/eip712.js +31 -5
  230. package/dist/types/eip712.js.map +1 -1
  231. package/dist/types/escrow.d.ts +17 -10
  232. package/dist/types/escrow.d.ts.map +1 -1
  233. package/dist/types/index.d.ts +5 -0
  234. package/dist/types/index.d.ts.map +1 -1
  235. package/dist/types/index.js +8 -0
  236. package/dist/types/index.js.map +1 -1
  237. package/dist/types/message.d.ts +32 -0
  238. package/dist/types/message.d.ts.map +1 -1
  239. package/dist/types/message.js +4 -0
  240. package/dist/types/message.js.map +1 -1
  241. package/dist/types/state.d.ts +28 -0
  242. package/dist/types/state.d.ts.map +1 -1
  243. package/dist/types/state.js +37 -6
  244. package/dist/types/state.js.map +1 -1
  245. package/dist/types/transaction.d.ts +17 -0
  246. package/dist/types/transaction.d.ts.map +1 -1
  247. package/dist/utils/ErrorRecoveryGuide.d.ts +125 -0
  248. package/dist/utils/ErrorRecoveryGuide.d.ts.map +1 -0
  249. package/dist/utils/ErrorRecoveryGuide.js +579 -0
  250. package/dist/utils/ErrorRecoveryGuide.js.map +1 -0
  251. package/dist/utils/Helpers.d.ts +453 -0
  252. package/dist/utils/Helpers.d.ts.map +1 -0
  253. package/dist/utils/Helpers.js +623 -0
  254. package/dist/utils/Helpers.js.map +1 -0
  255. package/dist/utils/IPFSClient.d.ts +113 -0
  256. package/dist/utils/IPFSClient.d.ts.map +1 -1
  257. package/dist/utils/IPFSClient.js +128 -7
  258. package/dist/utils/IPFSClient.js.map +1 -1
  259. package/dist/utils/Logger.d.ts +195 -0
  260. package/dist/utils/Logger.d.ts.map +1 -0
  261. package/dist/utils/Logger.js +382 -0
  262. package/dist/utils/Logger.js.map +1 -0
  263. package/dist/utils/NonceManager.d.ts +234 -1
  264. package/dist/utils/NonceManager.d.ts.map +1 -1
  265. package/dist/utils/NonceManager.js +372 -7
  266. package/dist/utils/NonceManager.js.map +1 -1
  267. package/dist/utils/RateLimiter.d.ts +253 -0
  268. package/dist/utils/RateLimiter.d.ts.map +1 -0
  269. package/dist/utils/RateLimiter.js +424 -0
  270. package/dist/utils/RateLimiter.js.map +1 -0
  271. package/dist/utils/ReceivedNonceTracker.d.ts +175 -0
  272. package/dist/utils/ReceivedNonceTracker.d.ts.map +1 -1
  273. package/dist/utils/ReceivedNonceTracker.js +261 -5
  274. package/dist/utils/ReceivedNonceTracker.js.map +1 -1
  275. package/dist/utils/SDKLifecycle.d.ts +156 -0
  276. package/dist/utils/SDKLifecycle.d.ts.map +1 -0
  277. package/dist/utils/SDKLifecycle.js +347 -0
  278. package/dist/utils/SDKLifecycle.js.map +1 -0
  279. package/dist/utils/SecureNonce.d.ts +57 -0
  280. package/dist/utils/SecureNonce.d.ts.map +1 -0
  281. package/dist/utils/SecureNonce.js +80 -0
  282. package/dist/utils/SecureNonce.js.map +1 -0
  283. package/dist/utils/Semaphore.d.ts +123 -0
  284. package/dist/utils/Semaphore.d.ts.map +1 -0
  285. package/dist/utils/Semaphore.js +247 -0
  286. package/dist/utils/Semaphore.js.map +1 -0
  287. package/dist/utils/UsedAttestationTracker.d.ts +167 -0
  288. package/dist/utils/UsedAttestationTracker.d.ts.map +1 -0
  289. package/dist/utils/UsedAttestationTracker.js +309 -0
  290. package/dist/utils/UsedAttestationTracker.js.map +1 -0
  291. package/dist/utils/canonicalJson.d.ts +22 -0
  292. package/dist/utils/canonicalJson.d.ts.map +1 -1
  293. package/dist/utils/canonicalJson.js +26 -3
  294. package/dist/utils/canonicalJson.js.map +1 -1
  295. package/dist/utils/computeTypeHash.d.ts +14 -0
  296. package/dist/utils/computeTypeHash.d.ts.map +1 -1
  297. package/dist/utils/computeTypeHash.js +19 -2
  298. package/dist/utils/computeTypeHash.js.map +1 -1
  299. package/dist/utils/fsSafe.d.ts +14 -0
  300. package/dist/utils/fsSafe.d.ts.map +1 -0
  301. package/dist/utils/fsSafe.js +89 -0
  302. package/dist/utils/fsSafe.js.map +1 -0
  303. package/dist/utils/index.d.ts +15 -0
  304. package/dist/utils/index.d.ts.map +1 -0
  305. package/dist/utils/index.js +51 -0
  306. package/dist/utils/index.js.map +1 -0
  307. package/dist/utils/security.d.ts +147 -0
  308. package/dist/utils/security.d.ts.map +1 -0
  309. package/dist/utils/security.js +391 -0
  310. package/dist/utils/security.js.map +1 -0
  311. package/dist/utils/validation.d.ts +40 -0
  312. package/dist/utils/validation.d.ts.map +1 -1
  313. package/dist/utils/validation.js +184 -7
  314. package/dist/utils/validation.js.map +1 -1
  315. package/package.json +54 -37
  316. package/src/ACTPClient.ts +692 -178
  317. package/src/abi/AgentRegistry.json +782 -0
  318. package/src/abi/EscrowVault.json +106 -38
  319. package/src/abi/IdentityRegistry.json +316 -0
  320. package/src/adapters/BaseAdapter.ts +473 -0
  321. package/src/adapters/BeginnerAdapter.ts +232 -0
  322. package/src/adapters/IntermediateAdapter.ts +316 -0
  323. package/src/adapters/index.ts +25 -0
  324. package/src/builders/DeliveryProofBuilder.ts +3 -2
  325. package/src/cli/commands/balance.ts +110 -0
  326. package/src/cli/commands/batch.ts +487 -0
  327. package/src/cli/commands/config.ts +231 -0
  328. package/src/cli/commands/init.ts +161 -0
  329. package/src/cli/commands/mint.ts +116 -0
  330. package/src/cli/commands/pay.ts +113 -0
  331. package/src/cli/commands/simulate.ts +345 -0
  332. package/src/cli/commands/time.ts +303 -0
  333. package/src/cli/commands/tx.ts +448 -0
  334. package/src/cli/commands/watch.ts +211 -0
  335. package/src/cli/index.ts +116 -0
  336. package/src/cli/utils/client.ts +249 -0
  337. package/src/cli/utils/config.ts +282 -0
  338. package/src/cli/utils/output.ts +465 -0
  339. package/src/config/networks.ts +32 -9
  340. package/src/errors/index.ts +298 -1
  341. package/src/index.ts +207 -71
  342. package/src/level0/Provider.ts +117 -0
  343. package/src/level0/ServiceDirectory.ts +131 -0
  344. package/src/level0/index.ts +10 -0
  345. package/src/level0/provide.ts +131 -0
  346. package/src/level0/request.ts +494 -0
  347. package/src/level1/Agent.ts +1432 -0
  348. package/src/level1/index.ts +10 -0
  349. package/src/level1/pricing/PriceCalculator.ts +255 -0
  350. package/src/level1/pricing/PricingStrategy.ts +198 -0
  351. package/src/level1/types/Job.ts +179 -0
  352. package/src/level1/types/Options.ts +291 -0
  353. package/src/level1/types/index.ts +8 -0
  354. package/src/protocol/ACTPKernel.ts +175 -23
  355. package/src/protocol/AgentRegistry.ts +559 -0
  356. package/src/protocol/DIDManager.ts +629 -0
  357. package/src/protocol/DIDResolver.ts +554 -0
  358. package/src/protocol/EASHelper.ts +230 -46
  359. package/src/protocol/EscrowVault.ts +68 -50
  360. package/src/protocol/EventMonitor.ts +44 -15
  361. package/src/protocol/MessageSigner.ts +193 -13
  362. package/src/protocol/ProofGenerator.ts +223 -4
  363. package/src/runtime/BlockchainRuntime.ts +993 -0
  364. package/src/runtime/IACTPRuntime.ts +284 -0
  365. package/src/runtime/MockRuntime.ts +1244 -0
  366. package/src/runtime/MockStateManager.ts +576 -0
  367. package/src/runtime/index.ts +25 -0
  368. package/src/runtime/types/MockState.ts +227 -0
  369. package/src/types/agent.ts +79 -0
  370. package/src/types/did.ts +223 -0
  371. package/src/types/escrow.ts +12 -11
  372. package/src/types/index.ts +5 -1
  373. package/src/types/state.ts +12 -3
  374. package/src/types/transaction.ts +4 -1
  375. package/src/utils/ErrorRecoveryGuide.ts +675 -0
  376. package/src/utils/Helpers.ts +688 -0
  377. package/src/utils/IPFSClient.ts +122 -5
  378. package/src/utils/Logger.ts +484 -0
  379. package/src/utils/NonceManager.ts +305 -8
  380. package/src/utils/RateLimiter.ts +534 -0
  381. package/src/utils/ReceivedNonceTracker.ts +170 -0
  382. package/src/utils/SDKLifecycle.ts +416 -0
  383. package/src/utils/SecureNonce.ts +78 -0
  384. package/src/utils/Semaphore.ts +276 -0
  385. package/src/utils/UsedAttestationTracker.ts +387 -0
  386. package/src/utils/fsSafe.ts +75 -0
  387. package/src/utils/index.ts +80 -0
  388. package/src/utils/security.ts +418 -0
  389. package/src/utils/validation.ts +164 -0
  390. package/src/__tests__/ProofGenerator.test.ts +0 -124
  391. package/src/__tests__/QuoteBuilder.test.ts +0 -516
  392. package/src/__tests__/StateMachine.test.ts +0 -82
  393. package/src/__tests__/builders/DeliveryProofBuilder.test.ts +0 -581
  394. package/src/__tests__/integration/ACTPClient.test.ts +0 -263
  395. package/src/__tests__/integration.test.ts +0 -289
  396. package/src/__tests__/protocol/EASHelper.test.ts +0 -472
  397. package/src/__tests__/protocol/EventMonitor.test.ts +0 -382
  398. package/src/__tests__/security/ACTPKernel.security.test.ts +0 -1167
  399. package/src/__tests__/security/EscrowVault.security.test.ts +0 -570
  400. package/src/__tests__/security/MessageSigner.security.test.ts +0 -286
  401. package/src/__tests__/security/NonceReplay.security.test.ts +0 -501
  402. package/src/__tests__/security/validation.security.test.ts +0 -376
  403. package/src/__tests__/utils/IPFSClient.test.ts +0 -262
  404. package/src/__tests__/utils/NonceManager.test.ts +0 -205
  405. package/src/__tests__/utils/canonicalJson.test.ts +0 -153
@@ -0,0 +1,1432 @@
1
+ /**
2
+ * Agent - Standard API for AI agents
3
+ *
4
+ * Provides agent-level abstractions: lifecycle, service provision,
5
+ * job handling, events, and statistics.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import { EventEmitter } from 'events';
11
+ import * as path from 'path';
12
+ import * as os from 'os';
13
+ import * as fs from 'fs';
14
+ import { ethers } from 'ethers';
15
+ import { ACTPClient } from '../ACTPClient';
16
+ import { Job, JobHandler, JobContext } from './types/Job';
17
+ import { ProvideOptions, RequestOptions, RequestResult, NetworkOption } from './types/Options';
18
+ import { PricingStrategy } from './pricing/PricingStrategy';
19
+ import { AgentLifecycleError, ServiceConfigError, ValidationError } from '../errors';
20
+ import { validateServiceName, validatePath, isValidAddress, LRUCache } from '../utils/security';
21
+ import { Logger } from '../utils/Logger';
22
+ import { ServiceHash } from '../utils/Helpers';
23
+ import { Semaphore } from '../utils/Semaphore';
24
+ import { ProofGenerator } from '../protocol/ProofGenerator';
25
+
26
+ /**
27
+ * Agent lifecycle states
28
+ */
29
+ export type AgentStatus = 'idle' | 'starting' | 'running' | 'paused' | 'stopping' | 'stopped';
30
+
31
+ /**
32
+ * Service filter configuration
33
+ */
34
+ export interface ServiceFilter {
35
+ /**
36
+ * Minimum budget in USDC (e.g., 5.00 for $5)
37
+ */
38
+ minBudget?: number;
39
+
40
+ /**
41
+ * Maximum budget in USDC (e.g., 100.00 for $100)
42
+ */
43
+ maxBudget?: number;
44
+
45
+ /**
46
+ * Custom filter function
47
+ */
48
+ custom?: (job: Job) => boolean;
49
+ }
50
+
51
+ /**
52
+ * Service configuration
53
+ */
54
+ export interface ServiceConfig {
55
+ /**
56
+ * Service name (e.g., 'translation', 'echo')
57
+ */
58
+ name: string;
59
+
60
+ /**
61
+ * Human-readable description
62
+ */
63
+ description?: string;
64
+
65
+ /**
66
+ * Pricing strategy (cost + margin model)
67
+ */
68
+ pricing?: PricingStrategy;
69
+
70
+ /**
71
+ * Service capabilities/tags
72
+ */
73
+ capabilities?: string[];
74
+
75
+ /**
76
+ * Job filter (function or filter config)
77
+ */
78
+ filter?: ServiceFilter | ((job: Job) => boolean);
79
+
80
+ /**
81
+ * Timeout per job (milliseconds)
82
+ */
83
+ timeout?: number;
84
+ }
85
+
86
+ /**
87
+ * Agent configuration
88
+ */
89
+ export interface AgentConfig {
90
+ /**
91
+ * Agent name
92
+ */
93
+ name: string;
94
+
95
+ /**
96
+ * Agent description
97
+ */
98
+ description?: string;
99
+
100
+ /**
101
+ * Wallet configuration
102
+ */
103
+ wallet?: 'auto' | 'connect' | string | { privateKey: string };
104
+
105
+ /**
106
+ * Network
107
+ */
108
+ network?: NetworkOption;
109
+
110
+ /**
111
+ * RPC URL for blockchain connection (required for testnet/mainnet)
112
+ *
113
+ * If not provided, defaults to public RPC from network config:
114
+ * - testnet: https://sepolia.base.org
115
+ * - mainnet: https://mainnet.base.org
116
+ *
117
+ * For production, consider using a dedicated RPC provider (Alchemy, Infura, etc.)
118
+ * for better reliability and rate limits.
119
+ *
120
+ * @example
121
+ * rpcUrl: 'https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY'
122
+ */
123
+ rpcUrl?: string;
124
+
125
+ /**
126
+ * State directory (mock mode only)
127
+ */
128
+ stateDirectory?: string;
129
+
130
+ /**
131
+ * Behavior configuration
132
+ */
133
+ behavior?: {
134
+ /**
135
+ * Auto-accept jobs
136
+ */
137
+ autoAccept?: boolean | ((job: Job) => boolean | Promise<boolean>);
138
+
139
+ /**
140
+ * Max concurrent jobs
141
+ */
142
+ concurrency?: number;
143
+
144
+ /**
145
+ * Retry configuration
146
+ */
147
+ retry?: {
148
+ attempts?: number;
149
+ delay?: number;
150
+ backoff?: 'linear' | 'exponential';
151
+ };
152
+ };
153
+
154
+ /**
155
+ * Persistence configuration
156
+ */
157
+ persistence?: {
158
+ enabled?: boolean;
159
+ path?: string;
160
+ };
161
+
162
+ /**
163
+ * Logging configuration
164
+ */
165
+ logging?: {
166
+ level?: 'debug' | 'info' | 'warn' | 'error';
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Agent statistics
172
+ */
173
+ export interface AgentStats {
174
+ jobsReceived: number;
175
+ jobsCompleted: number;
176
+ jobsFailed: number;
177
+ totalEarned: number;
178
+ totalSpent: number;
179
+ averageJobTime: number;
180
+ successRate: number;
181
+ }
182
+
183
+ /**
184
+ * Agent balance information
185
+ */
186
+ export interface AgentBalance {
187
+ eth: string;
188
+ usdc: string;
189
+ locked: string;
190
+ pending: string;
191
+ }
192
+
193
+ /**
194
+ * Agent class - Standard API
195
+ *
196
+ * Represents an autonomous AI agent that can provide services,
197
+ * request services from other agents, and manage its lifecycle.
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * const agent = new Agent({ name: 'Translator', network: 'mock' });
202
+ *
203
+ * agent.provide('translation', async (job, ctx) => {
204
+ * ctx.progress(50, 'Translating...');
205
+ * return { translated: translate(job.input.text) };
206
+ * });
207
+ *
208
+ * agent.on('payment:received', (amount) => {
209
+ * console.log(`Earned ${amount} USDC!`);
210
+ * });
211
+ *
212
+ * await agent.start();
213
+ * ```
214
+ */
215
+ export class Agent extends EventEmitter {
216
+ /**
217
+ * Agent name
218
+ */
219
+ public readonly name: string;
220
+
221
+ /**
222
+ * Agent description
223
+ */
224
+ public readonly description?: string;
225
+
226
+ /**
227
+ * Network the agent operates on
228
+ */
229
+ public readonly network: NetworkOption;
230
+
231
+ /**
232
+ * Current agent status
233
+ */
234
+ private _status: AgentStatus = 'idle';
235
+
236
+ /**
237
+ * ACTP Client instance
238
+ */
239
+ private _client?: ACTPClient;
240
+
241
+ /**
242
+ * Registered services
243
+ */
244
+ private services = new Map<string, { config: ServiceConfig; handler: JobHandler }>();
245
+
246
+ /**
247
+ * Active jobs
248
+ *
249
+ * SECURITY FIX (C-2): Changed from Map to LRUCache to prevent unbounded growth
250
+ * Maximum 1000 active jobs with LRU eviction
251
+ */
252
+ private activeJobs = new LRUCache<string, Job>(1000);
253
+
254
+ /**
255
+ * Processed job IDs (for deduplication)
256
+ *
257
+ * SECURITY FIX (C-1): Track jobs we've attempted to process
258
+ * This prevents race conditions where the same job is processed multiple times
259
+ * before the state transition completes
260
+ */
261
+ private processedJobs = new LRUCache<string, boolean>(10000);
262
+
263
+ /**
264
+ * Processing locks (for atomic locking)
265
+ *
266
+ * SECURITY FIX (C-1): Mutex for job processing.
267
+ * When we see a job, we IMMEDIATELY add to this set (atomic in single-threaded JS).
268
+ * This prevents race conditions where two poll cycles both pass the processedJobs.has()
269
+ * check before either calls processedJobs.set().
270
+ */
271
+ private processingLocks = new Set<string>();
272
+
273
+ /**
274
+ * Concurrency semaphore
275
+ *
276
+ * SECURITY FIX (MEDIUM-4): Limits concurrent job execution to prevent
277
+ * resource exhaustion (memory/CPU DoS). Uses behavior.concurrency setting.
278
+ */
279
+ private concurrencySemaphore!: Semaphore;
280
+
281
+ /**
282
+ * Statistics
283
+ */
284
+ private _stats: AgentStats = {
285
+ jobsReceived: 0,
286
+ jobsCompleted: 0,
287
+ jobsFailed: 0,
288
+ totalEarned: 0,
289
+ totalSpent: 0,
290
+ averageJobTime: 0,
291
+ successRate: 0,
292
+ };
293
+
294
+ /**
295
+ * Cached balance (updated periodically during polling)
296
+ */
297
+ private _balance: AgentBalance = {
298
+ eth: '0',
299
+ usdc: '0',
300
+ locked: '0',
301
+ pending: '0',
302
+ };
303
+
304
+ /**
305
+ * Configuration
306
+ */
307
+ private config: AgentConfig;
308
+
309
+ /**
310
+ * Polling interval ID (for job polling)
311
+ */
312
+ private pollingIntervalId?: NodeJS.Timeout;
313
+
314
+ /**
315
+ * Logger instance
316
+ */
317
+ private logger: Logger;
318
+
319
+ /**
320
+ * Creates a new Agent instance
321
+ *
322
+ * @param config - Agent configuration
323
+ */
324
+ constructor(config: AgentConfig) {
325
+ super();
326
+
327
+ if (!config.name) {
328
+ throw new ServiceConfigError('name', 'Agent name is required');
329
+ }
330
+
331
+ // SECURITY FIX (H-6): Use dedicated AGIRAILS directory as base
332
+ // This prevents writes anywhere in the project directory
333
+ const AGIRAILS_BASE = path.join(os.homedir(), '.agirails');
334
+
335
+ // Ensure base directory exists
336
+ if (!fs.existsSync(AGIRAILS_BASE)) {
337
+ fs.mkdirSync(AGIRAILS_BASE, { recursive: true });
338
+ }
339
+
340
+ // Validate state directory path if provided
341
+ if (config.stateDirectory) {
342
+ try {
343
+ // Validate the path is safe (no traversal, within AGIRAILS_BASE)
344
+ config.stateDirectory = validatePath(config.stateDirectory, AGIRAILS_BASE);
345
+ } catch (error) {
346
+ throw new ServiceConfigError(
347
+ 'stateDirectory',
348
+ `Invalid state directory: ${error instanceof Error ? error.message : String(error)}`
349
+ );
350
+ }
351
+ }
352
+
353
+ this.name = config.name;
354
+ this.description = config.description;
355
+ this.network = config.network || 'mock';
356
+ this.config = config;
357
+ this.logger = new Logger({
358
+ source: `Agent:${config.name}`,
359
+ minLevel: config.logging?.level || 'info',
360
+ });
361
+
362
+ // SECURITY FIX (MEDIUM-4): Initialize concurrency semaphore
363
+ // Default to 10 concurrent jobs if not specified
364
+ const maxConcurrency = config.behavior?.concurrency || 10;
365
+ this.concurrencySemaphore = new Semaphore(maxConcurrency);
366
+ this.logger.debug('Initialized concurrency semaphore', { maxConcurrency });
367
+ }
368
+
369
+ // =========================================================================
370
+ // Lifecycle Methods
371
+ // =========================================================================
372
+
373
+ /**
374
+ * Start the agent
375
+ *
376
+ * Initializes ACTP client and begins polling for jobs.
377
+ *
378
+ * @throws {AgentLifecycleError} If agent is not in idle or stopped state
379
+ */
380
+ async start(): Promise<void> {
381
+ if (this._status !== 'idle' && this._status !== 'stopped') {
382
+ throw new AgentLifecycleError(this._status, 'start');
383
+ }
384
+
385
+ this._status = 'starting';
386
+ this.emit('starting');
387
+
388
+ 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
+ let rpcUrl = this.config.rpcUrl;
393
+ if (!rpcUrl && (this.network === 'testnet' || this.network === 'mainnet')) {
394
+ // Import getNetwork to get default rpcUrl from network config
395
+ const { getNetwork } = await import('../config/networks');
396
+ const networkName = this.network === 'testnet' ? 'base-sepolia' : 'base-mainnet';
397
+ const networkConfig = getNetwork(networkName);
398
+ rpcUrl = networkConfig.rpcUrl;
399
+ this.logger.info(`Using default RPC URL for ${networkName}: ${rpcUrl}`);
400
+ }
401
+
402
+ // Initialize ACTP client
403
+ this._client = await ACTPClient.create({
404
+ mode: this.network === 'testnet' ? 'testnet' : this.network === 'mainnet' ? 'mainnet' : 'mock',
405
+ requesterAddress: this.address || this.generateAddress(),
406
+ stateDirectory: this.config.stateDirectory,
407
+ privateKey: this.getPrivateKey(),
408
+ rpcUrl,
409
+ });
410
+
411
+ // Start polling for jobs
412
+ this.startPolling();
413
+
414
+ this._status = 'running';
415
+ this.emit('started');
416
+ } catch (error) {
417
+ this._status = 'stopped';
418
+ this.emit('error', error);
419
+ throw error;
420
+ }
421
+ }
422
+
423
+ /**
424
+ * Stop the agent
425
+ *
426
+ * Stops polling and waits for active jobs to complete.
427
+ */
428
+ async stop(): Promise<void> {
429
+ if (this._status === 'stopped' || this._status === 'stopping') {
430
+ return;
431
+ }
432
+
433
+ this._status = 'stopping';
434
+ this.emit('stopping');
435
+
436
+ // Stop polling
437
+ this.stopPolling();
438
+
439
+ // Wait for active jobs to complete (with timeout)
440
+ await this.waitForActiveJobs(30000); // 30s timeout
441
+
442
+ this._status = 'stopped';
443
+ this.emit('stopped');
444
+ }
445
+
446
+ /**
447
+ * Pause the agent
448
+ *
449
+ * Stops accepting new jobs but keeps active jobs running.
450
+ */
451
+ pause(): void {
452
+ if (this._status !== 'running') {
453
+ throw new AgentLifecycleError(this._status, 'pause');
454
+ }
455
+
456
+ this.stopPolling();
457
+ this._status = 'paused';
458
+ this.emit('paused');
459
+ }
460
+
461
+ /**
462
+ * Resume the agent
463
+ *
464
+ * Resumes accepting new jobs after being paused.
465
+ */
466
+ resume(): void {
467
+ if (this._status !== 'paused') {
468
+ throw new AgentLifecycleError(this._status, 'resume');
469
+ }
470
+
471
+ this.startPolling();
472
+ this._status = 'running';
473
+ this.emit('resumed');
474
+ }
475
+
476
+ /**
477
+ * Restart the agent
478
+ */
479
+ async restart(): Promise<void> {
480
+ await this.stop();
481
+ await this.start();
482
+ }
483
+
484
+ // =========================================================================
485
+ // Service Registration
486
+ // =========================================================================
487
+
488
+ /**
489
+ * Register a service handler
490
+ *
491
+ * @param serviceOrConfig - Service name or full configuration
492
+ * @param handler - Job handler function
493
+ * @param options - Optional pricing/filter configuration
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * // Simple
498
+ * agent.provide('echo', async (job) => job.input);
499
+ *
500
+ * // With pricing
501
+ * agent.provide({
502
+ * name: 'translation',
503
+ * pricing: {
504
+ * cost: { base: 0.5, perUnit: { unit: 'word', rate: 0.005 } },
505
+ * margin: 0.40
506
+ * }
507
+ * }, async (job, ctx) => {
508
+ * // ... translation logic
509
+ * });
510
+ * ```
511
+ */
512
+ provide(
513
+ serviceOrConfig: string | ServiceConfig,
514
+ handler: JobHandler,
515
+ options?: Partial<ServiceConfig>
516
+ ): this {
517
+ const config: ServiceConfig =
518
+ typeof serviceOrConfig === 'string'
519
+ ? { name: serviceOrConfig, ...options }
520
+ : serviceOrConfig;
521
+
522
+ if (!config.name) {
523
+ throw new ServiceConfigError('name', 'Service name is required');
524
+ }
525
+
526
+ // SECURITY FIX (H-2): Validate service name to prevent injection
527
+ try {
528
+ config.name = validateServiceName(config.name);
529
+ } catch (error) {
530
+ throw new ServiceConfigError(
531
+ 'name',
532
+ `Invalid service name: ${error instanceof Error ? error.message : String(error)}`
533
+ );
534
+ }
535
+
536
+ if (this.services.has(config.name)) {
537
+ throw new ServiceConfigError('name', `Service "${config.name}" already registered`);
538
+ }
539
+
540
+ this.services.set(config.name, { config, handler });
541
+ this.emit('service:registered', config.name);
542
+ this.logger.info('Service registered', { service: config.name });
543
+
544
+ return this;
545
+ }
546
+
547
+ /**
548
+ * Request a service from another agent
549
+ *
550
+ * @param service - Service name
551
+ * @param options - Request options
552
+ * @returns Promise resolving to result
553
+ */
554
+ async request(service: string, options: Omit<RequestOptions, 'network'>): Promise<RequestResult> {
555
+ if (!this._client) {
556
+ throw new AgentLifecycleError(this._status, 'request (agent not started)');
557
+ }
558
+
559
+ // Import Basic API request function
560
+ const { request: basicRequest } = await import('../level0/request');
561
+
562
+ // Call Basic API request with agent's network
563
+ const result = await basicRequest(service, {
564
+ ...options,
565
+ network: this.network,
566
+ });
567
+
568
+ // Update stats
569
+ this._stats.totalSpent += options.budget;
570
+
571
+ return result;
572
+ }
573
+
574
+ // =========================================================================
575
+ // Properties
576
+ // =========================================================================
577
+
578
+ /**
579
+ * Current agent status
580
+ */
581
+ get status(): AgentStatus {
582
+ return this._status;
583
+ }
584
+
585
+ /**
586
+ * Agent's Ethereum address
587
+ */
588
+ get address(): string {
589
+ return this._client?.getAddress() || '';
590
+ }
591
+
592
+ /**
593
+ * Registered service names
594
+ */
595
+ get serviceNames(): string[] {
596
+ return Array.from(this.services.keys());
597
+ }
598
+
599
+ /**
600
+ * Active jobs
601
+ *
602
+ * SECURITY FIX (N-2): Now uses LRUCache.values() iterator.
603
+ * Returns a snapshot of currently active jobs.
604
+ */
605
+ get jobs(): Job[] {
606
+ return this.activeJobs.values();
607
+ }
608
+
609
+ /**
610
+ * Statistics
611
+ */
612
+ get stats(): AgentStats {
613
+ return { ...this._stats };
614
+ }
615
+
616
+ /**
617
+ * Get agent balance
618
+ *
619
+ * Returns current USDC balance plus locked/pending amounts from active transactions.
620
+ * Note: This is an async operation wrapped in a sync getter for convenience.
621
+ * For real-time balance, use getBalanceAsync() instead.
622
+ */
623
+ get balance(): AgentBalance {
624
+ // Return cached balance (updated periodically during polling)
625
+ return { ...this._balance };
626
+ }
627
+
628
+ /**
629
+ * Get agent balance asynchronously (real-time)
630
+ *
631
+ * @returns Promise resolving to current balance
632
+ */
633
+ async getBalanceAsync(): Promise<AgentBalance> {
634
+ if (!this._client?.runtime) {
635
+ return {
636
+ eth: '0',
637
+ usdc: '0',
638
+ locked: '0',
639
+ pending: '0',
640
+ };
641
+ }
642
+
643
+ try {
644
+ // Get USDC balance (if runtime supports it)
645
+ let usdc = '0';
646
+ if ('getBalance' in this._client.runtime) {
647
+ usdc = await (this._client.runtime as any).getBalance(this.address);
648
+ }
649
+
650
+ // Get transactions where this agent is provider
651
+ let locked = BigInt(0);
652
+ let pending = BigInt(0);
653
+
654
+ if ('getTransactionsByProvider' in this._client.runtime) {
655
+ // Get all active transactions for this provider
656
+ const allTx = await (this._client.runtime as any).getTransactionsByProvider(
657
+ this.address,
658
+ undefined, // all states
659
+ 1000
660
+ );
661
+
662
+ for (const tx of allTx) {
663
+ const amount = BigInt(tx.amount || '0');
664
+
665
+ // Locked: funds in escrow for active work (COMMITTED, IN_PROGRESS, DELIVERED)
666
+ if (tx.state === 'COMMITTED' || tx.state === 'IN_PROGRESS' || tx.state === 'DELIVERED') {
667
+ locked += amount;
668
+ }
669
+
670
+ // Pending: potential earnings waiting to be accepted (INITIATED, QUOTED)
671
+ if (tx.state === 'INITIATED' || tx.state === 'QUOTED') {
672
+ pending += amount;
673
+ }
674
+ }
675
+ }
676
+
677
+ this._balance = {
678
+ eth: '0', // ETH balance not tracked in USDC-only system
679
+ usdc: usdc,
680
+ locked: locked.toString(),
681
+ pending: pending.toString(),
682
+ };
683
+
684
+ return { ...this._balance };
685
+ } catch (error) {
686
+ this.logger.warn('Failed to fetch balance', { error });
687
+ return { ...this._balance };
688
+ }
689
+ }
690
+
691
+ /**
692
+ * ACTP Client reference (for advanced usage)
693
+ */
694
+ get client(): ACTPClient | undefined {
695
+ return this._client;
696
+ }
697
+
698
+ // =========================================================================
699
+ // Private Methods
700
+ // =========================================================================
701
+
702
+ /**
703
+ * Start polling for new jobs
704
+ */
705
+ private startPolling(): void {
706
+ if (this.pollingIntervalId) {
707
+ return; // Already polling
708
+ }
709
+
710
+ const pollingInterval = 5000; // 5 seconds
711
+ this.pollingIntervalId = setInterval(() => {
712
+ this.pollForJobs().catch((error) => {
713
+ this.emit('error', error);
714
+ });
715
+ }, pollingInterval);
716
+ }
717
+
718
+ /**
719
+ * Stop polling
720
+ */
721
+ private stopPolling(): void {
722
+ if (this.pollingIntervalId) {
723
+ clearInterval(this.pollingIntervalId);
724
+ this.pollingIntervalId = undefined;
725
+ }
726
+ }
727
+
728
+ /**
729
+ * Poll for new jobs
730
+ *
731
+ * SECURITY FIXES:
732
+ * - C-1: Race condition prevention using processedJobs deduplication
733
+ * - C-2: Memory leak prevention using LRUCache
734
+ * - H-1: DoS prevention by filtering transactions before loading all
735
+ * - H-4: Authorization checks for state transitions
736
+ *
737
+ * Queries runtime for transactions where this agent is the provider
738
+ * and the transaction is in INITIATED state (awaiting acceptance).
739
+ * For each pending transaction, creates a Job object and invokes
740
+ * the appropriate service handler.
741
+ */
742
+ private async pollForJobs(): Promise<void> {
743
+ if (!this._client) {
744
+ return; // Agent not started yet
745
+ }
746
+
747
+ try {
748
+ // SECURITY FIX (H-1): Use filtered query instead of getAllTransactions
749
+ // This prevents DoS via memory exhaustion by only fetching relevant transactions
750
+ let pendingJobs: any[] = [];
751
+
752
+ // Check if runtime has the filtered query method
753
+ if ('getTransactionsByProvider' in this._client.runtime) {
754
+ // Use optimized filtered query (max 100 jobs per poll)
755
+ pendingJobs = await (this._client.runtime as any).getTransactionsByProvider(
756
+ this.address,
757
+ 'INITIATED',
758
+ 100
759
+ );
760
+ } else {
761
+ // Fallback to getAllTransactions (for older runtime versions)
762
+ const allTransactions = await this._client.runtime.getAllTransactions();
763
+ pendingJobs = allTransactions.filter(
764
+ (tx) => tx.provider === this.address && tx.state === 'INITIATED'
765
+ );
766
+ }
767
+
768
+ this.logger.debug('Polling for jobs', {
769
+ pendingJobs: pendingJobs.length,
770
+ });
771
+
772
+ // Process each pending job
773
+ for (const tx of pendingJobs) {
774
+ try {
775
+ // SECURITY FIX (C-1): Check processingLocks first (atomic check)
776
+ // This prevents race conditions where two poll cycles both try to process
777
+ // the same job before either transitions the state
778
+ if (this.processingLocks.has(tx.id) || this.processedJobs.has(tx.id)) {
779
+ continue;
780
+ }
781
+
782
+ // IMMEDIATELY acquire lock (atomic in single-threaded JS)
783
+ this.processingLocks.add(tx.id);
784
+
785
+ // SECURITY FIX (C-2): Check if already in active jobs (LRUCache handles size limit)
786
+ if (this.activeJobs.has(tx.id)) {
787
+ this.processingLocks.delete(tx.id);
788
+ continue;
789
+ }
790
+
791
+ // SECURITY FIX (H-4): Verify this agent is authorized to accept this transaction
792
+ // Check that tx.provider matches our address (prevents unauthorized state transitions)
793
+ if (tx.provider !== this.address) {
794
+ this.logger.warn('Unauthorized transaction detected', {
795
+ txId: tx.id,
796
+ expectedProvider: this.address,
797
+ actualProvider: tx.provider,
798
+ });
799
+ this.processingLocks.delete(tx.id);
800
+ continue;
801
+ }
802
+
803
+ // Find matching service handler
804
+ const serviceHandler = this.findServiceHandler(tx);
805
+ if (!serviceHandler) {
806
+ // No handler registered for this service type
807
+ this.logger.debug('No handler for transaction', { txId: tx.id });
808
+ this.processingLocks.delete(tx.id);
809
+ continue;
810
+ }
811
+
812
+ // Check auto-accept behavior
813
+ const shouldAccept = await this.shouldAutoAccept(tx);
814
+ if (!shouldAccept) {
815
+ this.logger.debug('Auto-accept declined', { txId: tx.id });
816
+ this.processingLocks.delete(tx.id);
817
+ continue;
818
+ }
819
+
820
+ // Create Job object from transaction
821
+ const job = this.createJobFromTransaction(tx);
822
+
823
+ // SECURITY FIX (C-2): Add to active jobs (LRUCache prevents unbounded growth)
824
+ this.activeJobs.set(job.id, job);
825
+
826
+ // Link escrow immediately to transition out of INITIATED state
827
+ // This prevents polling from picking up this job again
828
+ try {
829
+ if (this._client && tx.state === 'INITIATED') {
830
+ await this._client.runtime.linkEscrow(tx.id, tx.amount);
831
+ }
832
+
833
+ // Successfully processed - mark as processed and release lock
834
+ this.processedJobs.set(job.id, true);
835
+ } catch (escrowError) {
836
+ // If linking escrow fails, remove from active jobs and release lock (allow retry)
837
+ this.activeJobs.delete(job.id);
838
+ this.logger.error('Failed to link escrow', { txId: tx.id }, escrowError as Error);
839
+ this.processingLocks.delete(tx.id);
840
+ continue;
841
+ } finally {
842
+ // Always release the lock
843
+ this.processingLocks.delete(tx.id);
844
+ }
845
+
846
+ this._stats.jobsReceived++;
847
+ this.emit('job:received', job);
848
+ this.logger.info('Job accepted', { jobId: job.id, service: job.service });
849
+
850
+ // Process the job asynchronously (don't await here to continue polling)
851
+ this.processJob(job, serviceHandler.handler).catch((error) => {
852
+ this.logger.error('Job processing failed', { jobId: job.id }, error as Error);
853
+ this.emit('error', error);
854
+ });
855
+ } catch (error) {
856
+ // Log error but continue processing other jobs
857
+ this.logger.error('Error processing pending job', { txId: tx.id }, error as Error);
858
+ this.emit('error', error);
859
+ }
860
+ }
861
+
862
+ // Update cached balance (non-blocking, don't await)
863
+ this.getBalanceAsync().catch(() => {
864
+ // Silently ignore balance update errors during polling
865
+ });
866
+ } catch (error) {
867
+ // Polling error - will retry on next interval
868
+ this.logger.error('Polling error', {}, error as Error);
869
+ this.emit('error', error);
870
+ }
871
+ }
872
+
873
+ /**
874
+ * Find service handler for a transaction
875
+ *
876
+ * SECURITY FIX (MEDIUM): Use exact field matching instead of substring search
877
+ * to prevent service routing spoofing attacks.
878
+ *
879
+ * Supports multiple formats (in priority order):
880
+ * 1. JSON: {"service":"name","input":...} - new structured format
881
+ * 2. Legacy: "service:name;input:..." - backward compatibility
882
+ * 3. Plain string exact match - simple service name
883
+ * 4. bytes32 hash - on-chain only (requires off-chain lookup)
884
+ */
885
+ private findServiceHandler(
886
+ tx: any
887
+ ): { config: ServiceConfig; handler: JobHandler } | undefined {
888
+ const serviceDesc = tx.serviceDescription;
889
+ if (!serviceDesc) {
890
+ return undefined;
891
+ }
892
+
893
+ let parsedService: string | undefined;
894
+
895
+ // 1. Try JSON format first (new structured format from request())
896
+ try {
897
+ const jsonMetadata = JSON.parse(serviceDesc);
898
+ if (jsonMetadata && typeof jsonMetadata.service === 'string') {
899
+ parsedService = jsonMetadata.service;
900
+ }
901
+ } catch {
902
+ // Not JSON, try other formats
903
+ }
904
+
905
+ // 2. Try legacy "service:NAME;input:JSON" format
906
+ if (!parsedService) {
907
+ const legacyMetadata = ServiceHash.fromLegacy(serviceDesc);
908
+ if (legacyMetadata) {
909
+ parsedService = legacyMetadata.service;
910
+ }
911
+ }
912
+
913
+ // 3. If we parsed a service name, do EXACT match
914
+ if (parsedService) {
915
+ const handler = this.services.get(parsedService);
916
+ if (handler) {
917
+ return handler;
918
+ }
919
+ }
920
+
921
+ // 4. Check if it's a bytes32 hash (from on-chain BlockchainRuntime)
922
+ // NOTE: For hashed metadata, the original data must be retrieved from
923
+ // event logs or off-chain storage. This is a fallback for hash-only matching.
924
+ if (ServiceHash.isValidHash(serviceDesc)) {
925
+ this.logger.debug('Service description is a hash - cannot extract service name', {
926
+ hash: serviceDesc.slice(0, 18) + '...',
927
+ });
928
+ // Cannot match hashes without original data
929
+ // In production, use event indexing to get original metadata
930
+ return undefined;
931
+ }
932
+
933
+ // 5. Fallback: Plain string exact match (service name directly)
934
+ for (const [serviceName, handler] of this.services.entries()) {
935
+ // EXACT match only - prevent substring spoofing
936
+ if (serviceDesc === serviceName) {
937
+ return handler;
938
+ }
939
+ }
940
+
941
+ return undefined;
942
+ }
943
+
944
+ /**
945
+ * Check if job should be auto-accepted
946
+ *
947
+ * SECURITY FIX (MVP): Added pricing strategy evaluation
948
+ * - Checks service-level filters (budget constraints)
949
+ * - Evaluates pricing strategy if configured
950
+ * - Only accepts jobs that meet pricing requirements
951
+ */
952
+ private async shouldAutoAccept(tx: any): Promise<boolean> {
953
+ // Get the service config for this transaction
954
+ const serviceHandler = this.findServiceHandler(tx);
955
+
956
+ // Check service-level filters first (budget constraints)
957
+ if (serviceHandler?.config.filter) {
958
+ const filter = serviceHandler.config.filter;
959
+ const budget = this.convertAmountToNumber(tx.amount);
960
+
961
+ // If filter is a ServiceFilter object, check budget constraints
962
+ if (typeof filter === 'object' && !Array.isArray(filter)) {
963
+ // Check minBudget
964
+ if (filter.minBudget !== undefined && budget < filter.minBudget) {
965
+ this.logger.debug('Job rejected: budget below minimum', {
966
+ txId: tx.id,
967
+ budget,
968
+ minBudget: filter.minBudget,
969
+ });
970
+ return false;
971
+ }
972
+
973
+ // Check maxBudget
974
+ if (filter.maxBudget !== undefined && budget > filter.maxBudget) {
975
+ this.logger.debug('Job rejected: budget above maximum', {
976
+ txId: tx.id,
977
+ budget,
978
+ maxBudget: filter.maxBudget,
979
+ });
980
+ return false;
981
+ }
982
+
983
+ // Check custom filter function
984
+ if (filter.custom && typeof filter.custom === 'function') {
985
+ const job = this.createJobFromTransaction(tx);
986
+ const customResult = filter.custom(job);
987
+ if (!customResult) {
988
+ this.logger.debug('Job rejected: custom filter declined', { txId: tx.id });
989
+ return false;
990
+ }
991
+ }
992
+ }
993
+ // If filter is a function (legacy support)
994
+ else if (typeof filter === 'function') {
995
+ const job = this.createJobFromTransaction(tx);
996
+ const filterResult = filter(job);
997
+ if (!filterResult) {
998
+ this.logger.debug('Job rejected: filter function declined', { txId: tx.id });
999
+ return false;
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ // MVP: Check pricing strategy if configured
1005
+ if (serviceHandler?.config.pricing) {
1006
+ const { calculatePrice } = await import('./pricing/PriceCalculator');
1007
+ const job = this.createJobFromTransaction(tx);
1008
+
1009
+ try {
1010
+ const calculation = calculatePrice(serviceHandler.config.pricing, job);
1011
+
1012
+ this.logger.debug('Pricing calculation', {
1013
+ txId: tx.id,
1014
+ cost: calculation.cost,
1015
+ price: calculation.price,
1016
+ profit: calculation.profit,
1017
+ margin: calculation.marginPercent,
1018
+ decision: calculation.decision,
1019
+ reason: calculation.reason,
1020
+ });
1021
+
1022
+ // Only accept if pricing decision is 'accept'
1023
+ if (calculation.decision === 'reject') {
1024
+ this.logger.info('Job rejected by pricing strategy', {
1025
+ txId: tx.id,
1026
+ reason: calculation.reason,
1027
+ });
1028
+ return false;
1029
+ }
1030
+
1031
+ // If decision is 'counter-offer', we could implement QUOTED state here
1032
+ // For MVP, we treat 'counter-offer' as reject (no automatic negotiation)
1033
+ if (calculation.decision === 'counter-offer') {
1034
+ this.logger.info('Job requires counter-offer (not implemented in MVP)', {
1035
+ txId: tx.id,
1036
+ reason: calculation.reason,
1037
+ });
1038
+ return false;
1039
+ }
1040
+ } catch (error) {
1041
+ // If pricing calculation fails, reject the job for safety
1042
+ this.logger.error('Pricing calculation failed, rejecting job', { txId: tx.id }, error as Error);
1043
+ return false;
1044
+ }
1045
+ }
1046
+
1047
+ // Check agent-level autoAccept behavior
1048
+ const autoAccept = this.config.behavior?.autoAccept;
1049
+
1050
+ if (autoAccept === undefined || autoAccept === true) {
1051
+ return true;
1052
+ }
1053
+
1054
+ if (autoAccept === false) {
1055
+ return false;
1056
+ }
1057
+
1058
+ // It's a function - evaluate it
1059
+ if (typeof autoAccept === 'function') {
1060
+ const job = this.createJobFromTransaction(tx);
1061
+ return await autoAccept(job);
1062
+ }
1063
+
1064
+ return false;
1065
+ }
1066
+
1067
+ /**
1068
+ * Create Job object from MockTransaction
1069
+ */
1070
+ private createJobFromTransaction(tx: any): Job {
1071
+ return {
1072
+ id: tx.id,
1073
+ service: this.extractServiceName(tx),
1074
+ input: this.extractJobInput(tx),
1075
+ budget: this.convertAmountToNumber(tx.amount),
1076
+ deadline: new Date(tx.deadline * 1000), // Convert unix timestamp to Date
1077
+ requester: tx.requester,
1078
+ metadata: this.extractMetadata(tx),
1079
+ };
1080
+ }
1081
+
1082
+ /**
1083
+ * Extract service name from transaction
1084
+ *
1085
+ * Supports multiple formats:
1086
+ * 1. JSON: {"service":"name","input":...}
1087
+ * 2. Legacy: "service:name;input:..."
1088
+ * 3. Plain string (service name directly)
1089
+ */
1090
+ private extractServiceName(tx: any): string {
1091
+ if (!tx.serviceDescription) {
1092
+ return 'unknown';
1093
+ }
1094
+
1095
+ // Try JSON format first (new structured format)
1096
+ try {
1097
+ const parsed = JSON.parse(tx.serviceDescription);
1098
+ if (parsed && typeof parsed.service === 'string') {
1099
+ return parsed.service;
1100
+ }
1101
+ } catch {
1102
+ // Not JSON, try other formats
1103
+ }
1104
+
1105
+ // Try legacy format: "service:serviceName;input:..."
1106
+ const legacyMatch = tx.serviceDescription.match(/^service:([^;]+)/);
1107
+ if (legacyMatch) {
1108
+ return legacyMatch[1];
1109
+ }
1110
+
1111
+ // Plain string - might be just the service name
1112
+ if (typeof tx.serviceDescription === 'string' && tx.serviceDescription.length < 64) {
1113
+ return tx.serviceDescription;
1114
+ }
1115
+
1116
+ return 'unknown';
1117
+ }
1118
+
1119
+ /**
1120
+ * Extract job input from transaction
1121
+ *
1122
+ * Supports multiple formats:
1123
+ * 1. JSON: {"service":"name","input":{...}}
1124
+ * 2. Legacy: "service:name;input:JSON"
1125
+ */
1126
+ private extractJobInput(tx: any): any {
1127
+ if (!tx.serviceDescription) {
1128
+ return {};
1129
+ }
1130
+
1131
+ // Try JSON format first (new structured format)
1132
+ try {
1133
+ const parsed = JSON.parse(tx.serviceDescription);
1134
+ if (parsed && parsed.input !== undefined) {
1135
+ return parsed.input;
1136
+ }
1137
+ } catch {
1138
+ // Not JSON, try other formats
1139
+ }
1140
+
1141
+ // Try legacy format: "service:serviceName;input:JSON"
1142
+ const legacyMatch = tx.serviceDescription.match(/;input:(.+)$/);
1143
+ if (legacyMatch) {
1144
+ try {
1145
+ return JSON.parse(legacyMatch[1]);
1146
+ } catch {
1147
+ return legacyMatch[1]; // Return as string if not valid JSON
1148
+ }
1149
+ }
1150
+
1151
+ return {};
1152
+ }
1153
+
1154
+ /**
1155
+ * Extract metadata from transaction
1156
+ */
1157
+ private extractMetadata(tx: any): Record<string, any> {
1158
+ return {
1159
+ transactionId: tx.id,
1160
+ createdAt: tx.createdAt,
1161
+ disputeWindow: tx.disputeWindow,
1162
+ };
1163
+ }
1164
+
1165
+ /**
1166
+ * Convert amount string to number (USDC has 6 decimals)
1167
+ */
1168
+ private convertAmountToNumber(amount: string): number {
1169
+ const amountBigInt = BigInt(amount);
1170
+ return Number(amountBigInt) / 1_000_000; // Convert from USDC wei to USDC
1171
+ }
1172
+
1173
+ /**
1174
+ * Process a job by invoking the handler
1175
+ *
1176
+ * SECURITY FIX (C-2): Always cleanup activeJobs on completion/failure
1177
+ * SECURITY FIX (MEDIUM-4): Uses semaphore to limit concurrent execution
1178
+ */
1179
+ private async processJob(job: Job, handler: JobHandler): Promise<void> {
1180
+ const startTime = Date.now();
1181
+
1182
+ // SECURITY FIX (MEDIUM-4): Check concurrency limit before processing
1183
+ // If semaphore is full, wait up to 30 seconds for a slot
1184
+ const CONCURRENCY_TIMEOUT_MS = 30000;
1185
+
1186
+ try {
1187
+ // Try to acquire semaphore permit (wait if at limit)
1188
+ await this.concurrencySemaphore.acquire(CONCURRENCY_TIMEOUT_MS);
1189
+ } catch (acquireError) {
1190
+ // Timeout waiting for concurrency slot
1191
+ this.logger.warn('Job rejected due to concurrency limit', {
1192
+ jobId: job.id,
1193
+ activeJobs: this.concurrencySemaphore.limit - this.concurrencySemaphore.availablePermits,
1194
+ maxConcurrency: this.concurrencySemaphore.limit,
1195
+ queueLength: this.concurrencySemaphore.queueLength,
1196
+ });
1197
+
1198
+ // Remove from active jobs since we couldn't process it
1199
+ this.activeJobs.delete(job.id);
1200
+ this.processedJobs.delete(job.id);
1201
+
1202
+ this.emit('job:rejected', job, 'concurrency_limit');
1203
+ throw new Error(
1204
+ `Job ${job.id} rejected: concurrency limit reached (${this.concurrencySemaphore.limit} concurrent jobs max). ` +
1205
+ `Try again later or increase behavior.concurrency.`
1206
+ );
1207
+ }
1208
+
1209
+ try {
1210
+ // Create job context
1211
+ const context = this.createJobContext(job);
1212
+
1213
+ // Invoke handler
1214
+ const result = await handler(job, context);
1215
+
1216
+ // SECURITY FIX (CRITICAL-2): Use ProofGenerator to create authenticated delivery proof
1217
+ // This ensures the proof has proper structure with txId, contentHash, and timestamp
1218
+ const proofGenerator = new ProofGenerator();
1219
+ const deliverable = typeof result === 'string' ? result : JSON.stringify(result);
1220
+ const deliveryProof = proofGenerator.generateDeliveryProof({
1221
+ txId: job.id,
1222
+ deliverable,
1223
+ metadata: {
1224
+ service: job.service,
1225
+ completedAt: Date.now(),
1226
+ },
1227
+ });
1228
+
1229
+ // Encode proof with content hash for verification
1230
+ const deliveryProofJson = JSON.stringify({
1231
+ ...deliveryProof,
1232
+ result, // Include original result for convenience
1233
+ });
1234
+
1235
+ // Transition transaction to DELIVERED state
1236
+ if (this._client) {
1237
+ // Store delivery proof by directly accessing MockRuntime's state
1238
+ // This is a workaround - in production, we'd use a proper method
1239
+ const runtime = this._client.runtime as any;
1240
+ if (runtime.stateManager) {
1241
+ await runtime.stateManager.withLock(async (state: any) => {
1242
+ const tx = state.transactions[job.id];
1243
+ if (tx) {
1244
+ tx.deliveryProof = deliveryProofJson;
1245
+ }
1246
+ });
1247
+ }
1248
+
1249
+ // Transition to DELIVERED (escrow was already linked in pollForJobs)
1250
+ await this._client.runtime.transitionState(job.id, 'DELIVERED');
1251
+ }
1252
+
1253
+ // SECURITY FIX (C-2): Remove from active jobs on SUCCESS
1254
+ this.activeJobs.delete(job.id);
1255
+
1256
+ // Update stats
1257
+ this._stats.jobsCompleted++;
1258
+ const duration = Date.now() - startTime;
1259
+ this._stats.averageJobTime =
1260
+ (this._stats.averageJobTime * (this._stats.jobsCompleted - 1) + duration) /
1261
+ this._stats.jobsCompleted;
1262
+ this._stats.successRate =
1263
+ this._stats.jobsCompleted / (this._stats.jobsCompleted + this._stats.jobsFailed);
1264
+ this._stats.totalEarned += job.budget;
1265
+
1266
+ // Emit events
1267
+ this.logger.info('Job completed', {
1268
+ jobId: job.id,
1269
+ duration,
1270
+ earned: job.budget,
1271
+ });
1272
+ this.emit('job:completed', job, result);
1273
+ this.emit('payment:received', job.budget);
1274
+ } catch (error) {
1275
+ // SECURITY FIX (C-2): Remove from active jobs on FAILURE
1276
+ this.activeJobs.delete(job.id);
1277
+ this._stats.jobsFailed++;
1278
+ this._stats.successRate =
1279
+ this._stats.jobsCompleted / (this._stats.jobsCompleted + this._stats.jobsFailed);
1280
+
1281
+ this.logger.error('Job failed', { jobId: job.id }, error as Error);
1282
+ this.emit('job:failed', job, error);
1283
+ } finally {
1284
+ // SECURITY FIX (MEDIUM-4): Always release semaphore permit
1285
+ this.concurrencySemaphore.release();
1286
+ }
1287
+ }
1288
+
1289
+ /**
1290
+ * Create JobContext for handler execution
1291
+ */
1292
+ private createJobContext(job: Job): JobContext {
1293
+ const state = new Map<string, any>();
1294
+ let cancelled = false;
1295
+ const cancelHandlers: Array<() => void> = [];
1296
+ const agent = this; // Capture 'this' for use in closures
1297
+
1298
+ return {
1299
+ agent: agent,
1300
+
1301
+ progress(percent: number, message?: string): void {
1302
+ // Emit progress event
1303
+ agent.emit('job:progress', job.id, percent, message);
1304
+ },
1305
+
1306
+ log: {
1307
+ debug: (message: string, meta?: any) => {
1308
+ if (agent.config.logging?.level === 'debug') {
1309
+ console.debug(`[${job.id}] ${message}`, meta);
1310
+ }
1311
+ },
1312
+ info: (message: string, meta?: any) => {
1313
+ if (['debug', 'info'].includes(agent.config.logging?.level || 'info')) {
1314
+ console.info(`[${job.id}] ${message}`, meta);
1315
+ }
1316
+ },
1317
+ warn: (message: string, meta?: any) => {
1318
+ if (['debug', 'info', 'warn'].includes(agent.config.logging?.level || 'info')) {
1319
+ console.warn(`[${job.id}] ${message}`, meta);
1320
+ }
1321
+ },
1322
+ error: (message: string, meta?: any) => {
1323
+ console.error(`[${job.id}] ${message}`, meta);
1324
+ },
1325
+ },
1326
+
1327
+ state: {
1328
+ get<T>(key: string): T | undefined {
1329
+ return state.get(key);
1330
+ },
1331
+ set<T>(key: string, value: T): void {
1332
+ state.set(key, value);
1333
+ },
1334
+ },
1335
+
1336
+ get cancelled(): boolean {
1337
+ return cancelled;
1338
+ },
1339
+
1340
+ onCancel(handler: () => void): void {
1341
+ cancelHandlers.push(handler);
1342
+ },
1343
+ };
1344
+ }
1345
+
1346
+ /**
1347
+ * Wait for active jobs to complete
1348
+ */
1349
+ private async waitForActiveJobs(timeoutMs: number): Promise<void> {
1350
+ const startTime = Date.now();
1351
+
1352
+ while (this.activeJobs.size > 0) {
1353
+ if (Date.now() - startTime > timeoutMs) {
1354
+ this.logger.warn('Active jobs still running after timeout', {
1355
+ activeJobs: this.activeJobs.size,
1356
+ });
1357
+ break;
1358
+ }
1359
+
1360
+ await new Promise((resolve) => setTimeout(resolve, 100));
1361
+ }
1362
+ }
1363
+
1364
+ /**
1365
+ * Generate address based on wallet configuration
1366
+ *
1367
+ * SECURITY FIX (HIGH): For testnet/mainnet, MUST derive from private key.
1368
+ * For mock mode, can use deterministic address for convenience.
1369
+ */
1370
+ private generateAddress(): string {
1371
+ // If wallet has private key, ALWAYS derive address from it
1372
+ const privateKey = this.getPrivateKey();
1373
+ if (privateKey) {
1374
+ try {
1375
+ const wallet = new ethers.Wallet(privateKey);
1376
+ return wallet.address.toLowerCase();
1377
+ } catch (error) {
1378
+ throw new ValidationError('wallet', 'Invalid private key format');
1379
+ }
1380
+ }
1381
+
1382
+ // For non-mock networks, require a valid private key or address
1383
+ if (this.network === 'testnet' || this.network === 'mainnet') {
1384
+ throw new ValidationError(
1385
+ 'wallet',
1386
+ `${this.network} mode requires a valid private key or address in wallet configuration`
1387
+ );
1388
+ }
1389
+
1390
+ // For mock mode only: generate deterministic address from agent name
1391
+ // This is safe because mock mode doesn't involve real funds
1392
+ return `0x${Buffer.from(this.name).toString('hex').padEnd(40, '0').slice(0, 40)}`;
1393
+ }
1394
+
1395
+ /**
1396
+ * Get private key from configuration
1397
+ *
1398
+ * SECURITY FIX (HIGH): Validate private key format before use
1399
+ */
1400
+ private getPrivateKey(): string | undefined {
1401
+ if (!this.config.wallet || this.config.wallet === 'auto' || this.config.wallet === 'connect') {
1402
+ return undefined;
1403
+ }
1404
+
1405
+ if (typeof this.config.wallet === 'string') {
1406
+ // Check if it looks like a private key (0x + 64 hex chars)
1407
+ if (/^0x[0-9a-fA-F]{64}$/.test(this.config.wallet)) {
1408
+ // Validate by trying to create a wallet
1409
+ try {
1410
+ new ethers.Wallet(this.config.wallet);
1411
+ return this.config.wallet;
1412
+ } catch {
1413
+ throw new ValidationError('wallet', 'Invalid private key format');
1414
+ }
1415
+ }
1416
+ // It's an address, not a private key
1417
+ return undefined;
1418
+ }
1419
+
1420
+ // Validate private key format
1421
+ if (this.config.wallet.privateKey) {
1422
+ try {
1423
+ new ethers.Wallet(this.config.wallet.privateKey);
1424
+ return this.config.wallet.privateKey;
1425
+ } catch {
1426
+ throw new ValidationError('wallet.privateKey', 'Invalid private key format');
1427
+ }
1428
+ }
1429
+
1430
+ return undefined;
1431
+ }
1432
+ }