@agirails/sdk 3.2.0 → 3.4.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 (278) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +12 -14
  3. package/dist/ACTPClient.d.ts +8 -11
  4. package/dist/ACTPClient.d.ts.map +1 -1
  5. package/dist/ACTPClient.js +79 -20
  6. package/dist/ACTPClient.js.map +1 -1
  7. package/dist/__tests__/helpers/mockX402Server.d.ts +67 -0
  8. package/dist/__tests__/helpers/mockX402Server.d.ts.map +1 -0
  9. package/dist/__tests__/helpers/mockX402Server.js +121 -0
  10. package/dist/__tests__/helpers/mockX402Server.js.map +1 -0
  11. package/dist/adapters/BaseAdapter.d.ts +7 -1
  12. package/dist/adapters/BaseAdapter.d.ts.map +1 -1
  13. package/dist/adapters/BaseAdapter.js +11 -6
  14. package/dist/adapters/BaseAdapter.js.map +1 -1
  15. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  16. package/dist/adapters/BasicAdapter.js +12 -2
  17. package/dist/adapters/BasicAdapter.js.map +1 -1
  18. package/dist/adapters/StandardAdapter.d.ts.map +1 -1
  19. package/dist/adapters/StandardAdapter.js +12 -2
  20. package/dist/adapters/StandardAdapter.js.map +1 -1
  21. package/dist/adapters/X402Adapter.d.ts +161 -199
  22. package/dist/adapters/X402Adapter.d.ts.map +1 -1
  23. package/dist/adapters/X402Adapter.js +603 -414
  24. package/dist/adapters/X402Adapter.js.map +1 -1
  25. package/dist/adapters/index.d.ts +1 -1
  26. package/dist/adapters/index.d.ts.map +1 -1
  27. package/dist/adapters/index.js.map +1 -1
  28. package/dist/api/agirailsApp.d.ts +21 -1
  29. package/dist/api/agirailsApp.d.ts.map +1 -1
  30. package/dist/api/agirailsApp.js.map +1 -1
  31. package/dist/builders/CounterAcceptBuilder.d.ts +96 -0
  32. package/dist/builders/CounterAcceptBuilder.d.ts.map +1 -0
  33. package/dist/builders/CounterAcceptBuilder.js +226 -0
  34. package/dist/builders/CounterAcceptBuilder.js.map +1 -0
  35. package/dist/builders/CounterOfferBuilder.d.ts +143 -0
  36. package/dist/builders/CounterOfferBuilder.d.ts.map +1 -0
  37. package/dist/builders/CounterOfferBuilder.js +329 -0
  38. package/dist/builders/CounterOfferBuilder.js.map +1 -0
  39. package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -1
  40. package/dist/builders/DeliveryProofBuilder.js +3 -2
  41. package/dist/builders/DeliveryProofBuilder.js.map +1 -1
  42. package/dist/builders/QuoteBuilder.d.ts.map +1 -1
  43. package/dist/builders/QuoteBuilder.js +8 -3
  44. package/dist/builders/QuoteBuilder.js.map +1 -1
  45. package/dist/builders/index.d.ts +2 -0
  46. package/dist/builders/index.d.ts.map +1 -1
  47. package/dist/builders/index.js +7 -1
  48. package/dist/builders/index.js.map +1 -1
  49. package/dist/cli/agirails.js +34 -6
  50. package/dist/cli/agirails.js.map +1 -1
  51. package/dist/cli/commands/autopublish.js +9 -1
  52. package/dist/cli/commands/autopublish.js.map +1 -1
  53. package/dist/cli/commands/config.js +1 -12
  54. package/dist/cli/commands/config.js.map +1 -1
  55. package/dist/cli/commands/deploy-env.js +1 -1
  56. package/dist/cli/commands/deploy-env.js.map +1 -1
  57. package/dist/cli/commands/diff.js +38 -4
  58. package/dist/cli/commands/diff.js.map +1 -1
  59. package/dist/cli/commands/health.js +24 -6
  60. package/dist/cli/commands/health.js.map +1 -1
  61. package/dist/cli/commands/init.d.ts +2 -0
  62. package/dist/cli/commands/init.d.ts.map +1 -1
  63. package/dist/cli/commands/init.js +100 -7
  64. package/dist/cli/commands/init.js.map +1 -1
  65. package/dist/cli/commands/pay.d.ts.map +1 -1
  66. package/dist/cli/commands/pay.js +23 -0
  67. package/dist/cli/commands/pay.js.map +1 -1
  68. package/dist/cli/commands/publish.d.ts +34 -0
  69. package/dist/cli/commands/publish.d.ts.map +1 -1
  70. package/dist/cli/commands/publish.js +266 -83
  71. package/dist/cli/commands/publish.js.map +1 -1
  72. package/dist/cli/commands/pull.js +3 -1
  73. package/dist/cli/commands/pull.js.map +1 -1
  74. package/dist/cli/commands/receipt.d.ts +17 -3
  75. package/dist/cli/commands/receipt.d.ts.map +1 -1
  76. package/dist/cli/commands/receipt.js +95 -33
  77. package/dist/cli/commands/receipt.js.map +1 -1
  78. package/dist/cli/commands/repair.d.ts +23 -0
  79. package/dist/cli/commands/repair.d.ts.map +1 -0
  80. package/dist/cli/commands/repair.js +210 -0
  81. package/dist/cli/commands/repair.js.map +1 -0
  82. package/dist/cli/commands/serve.d.ts +38 -0
  83. package/dist/cli/commands/serve.d.ts.map +1 -0
  84. package/dist/cli/commands/serve.js +308 -0
  85. package/dist/cli/commands/serve.js.map +1 -0
  86. package/dist/cli/commands/test.d.ts.map +1 -1
  87. package/dist/cli/commands/test.js +222 -60
  88. package/dist/cli/commands/test.js.map +1 -1
  89. package/dist/cli/commands/tx.js +13 -0
  90. package/dist/cli/commands/tx.js.map +1 -1
  91. package/dist/cli/index.js +9 -1
  92. package/dist/cli/index.js.map +1 -1
  93. package/dist/cli/receiptUpload.d.ts +52 -0
  94. package/dist/cli/receiptUpload.d.ts.map +1 -0
  95. package/dist/cli/receiptUpload.js +134 -0
  96. package/dist/cli/receiptUpload.js.map +1 -0
  97. package/dist/cli/utils/banner.d.ts +31 -0
  98. package/dist/cli/utils/banner.d.ts.map +1 -0
  99. package/dist/cli/utils/banner.js +92 -0
  100. package/dist/cli/utils/banner.js.map +1 -0
  101. package/dist/cli/utils/config.d.ts +0 -2
  102. package/dist/cli/utils/config.d.ts.map +1 -1
  103. package/dist/cli/utils/config.js +40 -25
  104. package/dist/cli/utils/config.js.map +1 -1
  105. package/dist/cli/utils/output.d.ts +2 -0
  106. package/dist/cli/utils/output.d.ts.map +1 -1
  107. package/dist/cli/utils/output.js +7 -1
  108. package/dist/cli/utils/output.js.map +1 -1
  109. package/dist/cli/utils/share.d.ts +51 -0
  110. package/dist/cli/utils/share.d.ts.map +1 -0
  111. package/dist/cli/utils/share.js +133 -0
  112. package/dist/cli/utils/share.js.map +1 -0
  113. package/dist/config/agirailsmd.d.ts.map +1 -1
  114. package/dist/config/agirailsmd.js +2 -1
  115. package/dist/config/agirailsmd.js.map +1 -1
  116. package/dist/config/agirailsmdV4.d.ts +46 -1
  117. package/dist/config/agirailsmdV4.d.ts.map +1 -1
  118. package/dist/config/agirailsmdV4.js +65 -8
  119. package/dist/config/agirailsmdV4.js.map +1 -1
  120. package/dist/config/defaults.d.ts +12 -2
  121. package/dist/config/defaults.d.ts.map +1 -1
  122. package/dist/config/defaults.js +19 -3
  123. package/dist/config/defaults.js.map +1 -1
  124. package/dist/config/networks.d.ts +7 -0
  125. package/dist/config/networks.d.ts.map +1 -1
  126. package/dist/config/networks.js +20 -11
  127. package/dist/config/networks.js.map +1 -1
  128. package/dist/config/pendingPublish.d.ts.map +1 -1
  129. package/dist/config/pendingPublish.js +10 -3
  130. package/dist/config/pendingPublish.js.map +1 -1
  131. package/dist/config/publishPipeline.d.ts +23 -1
  132. package/dist/config/publishPipeline.d.ts.map +1 -1
  133. package/dist/config/publishPipeline.js +70 -15
  134. package/dist/config/publishPipeline.js.map +1 -1
  135. package/dist/config/syncOperations.d.ts.map +1 -1
  136. package/dist/config/syncOperations.js +4 -2
  137. package/dist/config/syncOperations.js.map +1 -1
  138. package/dist/erc8004/ERC8004Bridge.d.ts.map +1 -1
  139. package/dist/erc8004/ERC8004Bridge.js +0 -1
  140. package/dist/erc8004/ERC8004Bridge.js.map +1 -1
  141. package/dist/errors/ACTPError.d.ts +24 -0
  142. package/dist/errors/ACTPError.d.ts.map +1 -0
  143. package/dist/errors/ACTPError.js +35 -0
  144. package/dist/errors/ACTPError.js.map +1 -0
  145. package/dist/errors/X402Errors.d.ts +106 -0
  146. package/dist/errors/X402Errors.d.ts.map +1 -0
  147. package/dist/errors/X402Errors.js +160 -0
  148. package/dist/errors/X402Errors.js.map +1 -0
  149. package/dist/errors/index.d.ts +3 -9
  150. package/dist/errors/index.d.ts.map +1 -1
  151. package/dist/errors/index.js +38 -33
  152. package/dist/errors/index.js.map +1 -1
  153. package/dist/index.d.ts +22 -1
  154. package/dist/index.d.ts.map +1 -1
  155. package/dist/index.js +41 -3
  156. package/dist/index.js.map +1 -1
  157. package/dist/level0/Provider.d.ts +5 -0
  158. package/dist/level0/Provider.d.ts.map +1 -1
  159. package/dist/level0/ServiceDirectory.d.ts.map +1 -1
  160. package/dist/level0/ServiceDirectory.js +3 -2
  161. package/dist/level0/ServiceDirectory.js.map +1 -1
  162. package/dist/level0/provide.d.ts.map +1 -1
  163. package/dist/level0/provide.js +11 -8
  164. package/dist/level0/provide.js.map +1 -1
  165. package/dist/level0/request.d.ts.map +1 -1
  166. package/dist/level0/request.js +14 -6
  167. package/dist/level0/request.js.map +1 -1
  168. package/dist/level1/Agent.d.ts +28 -1
  169. package/dist/level1/Agent.d.ts.map +1 -1
  170. package/dist/level1/Agent.js +89 -12
  171. package/dist/level1/Agent.js.map +1 -1
  172. package/dist/level1/pricing/PriceCalculator.d.ts.map +1 -1
  173. package/dist/level1/pricing/PriceCalculator.js +4 -12
  174. package/dist/level1/pricing/PriceCalculator.js.map +1 -1
  175. package/dist/negotiation/BuyerOrchestrator.d.ts +103 -1
  176. package/dist/negotiation/BuyerOrchestrator.d.ts.map +1 -1
  177. package/dist/negotiation/BuyerOrchestrator.js +499 -4
  178. package/dist/negotiation/BuyerOrchestrator.js.map +1 -1
  179. package/dist/negotiation/DecisionEngine.d.ts +69 -1
  180. package/dist/negotiation/DecisionEngine.d.ts.map +1 -1
  181. package/dist/negotiation/DecisionEngine.js +140 -1
  182. package/dist/negotiation/DecisionEngine.js.map +1 -1
  183. package/dist/negotiation/PolicyEngine.d.ts +32 -0
  184. package/dist/negotiation/PolicyEngine.d.ts.map +1 -1
  185. package/dist/negotiation/PolicyEngine.js.map +1 -1
  186. package/dist/negotiation/ProviderOrchestrator.d.ts +108 -0
  187. package/dist/negotiation/ProviderOrchestrator.d.ts.map +1 -0
  188. package/dist/negotiation/ProviderOrchestrator.js +136 -0
  189. package/dist/negotiation/ProviderOrchestrator.js.map +1 -0
  190. package/dist/negotiation/ProviderPolicy.d.ts +143 -0
  191. package/dist/negotiation/ProviderPolicy.d.ts.map +1 -0
  192. package/dist/negotiation/ProviderPolicy.js +207 -0
  193. package/dist/negotiation/ProviderPolicy.js.map +1 -0
  194. package/dist/negotiation/index.d.ts +8 -1
  195. package/dist/negotiation/index.d.ts.map +1 -1
  196. package/dist/negotiation/index.js +8 -1
  197. package/dist/negotiation/index.js.map +1 -1
  198. package/dist/negotiation/verifyQuoteOnChain.d.ts +58 -0
  199. package/dist/negotiation/verifyQuoteOnChain.d.ts.map +1 -0
  200. package/dist/negotiation/verifyQuoteOnChain.js +83 -0
  201. package/dist/negotiation/verifyQuoteOnChain.js.map +1 -0
  202. package/dist/protocol/ACTPKernel.d.ts +4 -1
  203. package/dist/protocol/ACTPKernel.d.ts.map +1 -1
  204. package/dist/protocol/ACTPKernel.js +2 -1
  205. package/dist/protocol/ACTPKernel.js.map +1 -1
  206. package/dist/protocol/EventMonitor.d.ts +27 -1
  207. package/dist/protocol/EventMonitor.d.ts.map +1 -1
  208. package/dist/protocol/EventMonitor.js +11 -9
  209. package/dist/protocol/EventMonitor.js.map +1 -1
  210. package/dist/protocol/ProofGenerator.d.ts.map +1 -1
  211. package/dist/protocol/ProofGenerator.js +3 -2
  212. package/dist/protocol/ProofGenerator.js.map +1 -1
  213. package/dist/runtime/BlockchainRuntime.d.ts +15 -0
  214. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  215. package/dist/runtime/BlockchainRuntime.js +42 -6
  216. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  217. package/dist/runtime/IACTPRuntime.d.ts +35 -0
  218. package/dist/runtime/IACTPRuntime.d.ts.map +1 -1
  219. package/dist/runtime/MockRuntime.d.ts +14 -2
  220. package/dist/runtime/MockRuntime.d.ts.map +1 -1
  221. package/dist/runtime/MockRuntime.js +55 -22
  222. package/dist/runtime/MockRuntime.js.map +1 -1
  223. package/dist/runtime/types/MockState.d.ts +14 -0
  224. package/dist/runtime/types/MockState.d.ts.map +1 -1
  225. package/dist/runtime/types/MockState.js.map +1 -1
  226. package/dist/server/buildX402Server.d.ts +131 -0
  227. package/dist/server/buildX402Server.d.ts.map +1 -0
  228. package/dist/server/buildX402Server.js +151 -0
  229. package/dist/server/buildX402Server.js.map +1 -0
  230. package/dist/server/index.d.ts +33 -0
  231. package/dist/server/index.d.ts.map +1 -0
  232. package/dist/server/index.js +36 -0
  233. package/dist/server/index.js.map +1 -0
  234. package/dist/transport/QuoteChannel.d.ts +201 -0
  235. package/dist/transport/QuoteChannel.d.ts.map +1 -0
  236. package/dist/transport/QuoteChannel.js +358 -0
  237. package/dist/transport/QuoteChannel.js.map +1 -0
  238. package/dist/types/adapter.d.ts +64 -34
  239. package/dist/types/adapter.d.ts.map +1 -1
  240. package/dist/types/adapter.js +6 -1
  241. package/dist/types/adapter.js.map +1 -1
  242. package/dist/types/eip712.d.ts +20 -0
  243. package/dist/types/eip712.d.ts.map +1 -1
  244. package/dist/types/x402.d.ts +8 -8
  245. package/dist/utils/security.d.ts.map +1 -1
  246. package/dist/utils/security.js +4 -6
  247. package/dist/utils/security.js.map +1 -1
  248. package/dist/wallet/AutoWalletProvider.d.ts +45 -1
  249. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
  250. package/dist/wallet/AutoWalletProvider.js +154 -1
  251. package/dist/wallet/AutoWalletProvider.js.map +1 -1
  252. package/dist/wallet/EOAWalletProvider.d.ts +13 -1
  253. package/dist/wallet/EOAWalletProvider.d.ts.map +1 -1
  254. package/dist/wallet/EOAWalletProvider.js +24 -0
  255. package/dist/wallet/EOAWalletProvider.js.map +1 -1
  256. package/dist/wallet/IWalletProvider.d.ts +34 -0
  257. package/dist/wallet/IWalletProvider.d.ts.map +1 -1
  258. package/dist/wallet/SmartWalletRouter.d.ts.map +1 -1
  259. package/dist/wallet/SmartWalletRouter.js +3 -1
  260. package/dist/wallet/SmartWalletRouter.js.map +1 -1
  261. package/dist/wallet/aa/BundlerClient.js +8 -4
  262. package/dist/wallet/aa/BundlerClient.js.map +1 -1
  263. package/dist/wallet/aa/DualNonceManager.d.ts +4 -1
  264. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
  265. package/dist/wallet/aa/DualNonceManager.js +3 -0
  266. package/dist/wallet/aa/DualNonceManager.js.map +1 -1
  267. package/dist/wallet/keystore.d.ts.map +1 -1
  268. package/dist/wallet/keystore.js +6 -4
  269. package/dist/wallet/keystore.js.map +1 -1
  270. package/package.json +31 -3
  271. package/dist/adapters/BeginnerAdapter.d.ts +0 -152
  272. package/dist/adapters/BeginnerAdapter.d.ts.map +0 -1
  273. package/dist/adapters/BeginnerAdapter.js +0 -168
  274. package/dist/adapters/BeginnerAdapter.js.map +0 -1
  275. package/dist/adapters/IntermediateAdapter.d.ts +0 -211
  276. package/dist/adapters/IntermediateAdapter.d.ts.map +0 -1
  277. package/dist/adapters/IntermediateAdapter.js +0 -260
  278. package/dist/adapters/IntermediateAdapter.js.map +0 -1
@@ -1,511 +1,700 @@
1
1
  "use strict";
2
2
  /**
3
- * X402Adapter - HTTP 402 Payment Required Protocol (Atomic Payments)
3
+ * X402Adapter real x402 v2 protocol support.
4
4
  *
5
- * Implements the x402 protocol for atomic, instant API payments.
6
- * NO escrow, NO state machine, NO disputes - just pay and receive.
5
+ * Thin wrapper around official @x402/fetch + @x402/evm + @x402/core packages.
6
+ * Replaces the legacy custom `x-payment-*` HTTP flow (which was never real x402)
7
+ * with proper EIP-3009 / Permit2 wire format for full interoperability with
8
+ * any x402 v2 seller (Coinbase demo, third-party servers, AGIRAILS sellers).
7
9
  *
8
- * This is fundamentally different from ACTP:
9
- * - ACTP: escrow state machine disputes → explicit release
10
- * - x402: atomic payment instant settlement done
10
+ * Architecture:
11
+ * - Buyer signs EIP-3009 authorization or Permit2 witness OFF-CHAIN
12
+ * - Facilitator (server-configured) submits on-chain tx and pays gas
13
+ * - Buyer is always gassless for x402 by protocol design
14
+ * - Smart Wallet buyers use Permit2 path (ERC-1271 + ERC-6492 via viem)
15
+ * - EOA buyers use either path
11
16
  *
12
- * Use x402 for:
13
- * - Simple API calls (pay-per-request)
14
- * - Instant delivery (response IS the delivery)
15
- * - Low-value, high-frequency transactions
17
+ * Zero fee layer: payTo goes directly to seller. X402Relay is never used.
18
+ * Zero reputation hooks: ERC-8004 registry never touched on x402 payments.
16
19
  *
17
- * Use ACTP for:
18
- * - Complex services requiring verification
19
- * - High-value transactions needing dispute protection
20
- * - Multi-step deliveries
20
+ * Architecture rationale and Smart Wallet signing flow are documented in
21
+ * the project's internal x402 v2 implementation notes.
21
22
  *
22
23
  * @module adapters/X402Adapter
23
24
  */
24
25
  Object.defineProperty(exports, "__esModule", { value: true });
25
26
  exports.X402Adapter = void 0;
26
- const BaseAdapter_1 = require("./BaseAdapter");
27
- const x402_1 = require("../types/x402");
28
- // ============================================================================
29
- // X402Adapter Implementation
30
- // ============================================================================
27
+ const fetch_1 = require("@x402/fetch");
28
+ const evm_1 = require("@x402/evm");
29
+ const X402Errors_1 = require("../errors/X402Errors");
30
+ const Logger_1 = require("../utils/Logger");
31
31
  /**
32
- * X402Adapter - Atomic HTTP payment protocol.
33
- *
34
- * Key characteristics:
35
- * - usesEscrow: false (direct payment)
36
- * - supportsDisputes: false (atomic = final)
37
- * - settlementMode: 'atomic' (instant)
38
- * - releaseRequired: false (no escrow to release)
32
+ * Default networks if allowedNetworks is undefined.
39
33
  *
40
- * @example
41
- * ```typescript
42
- * const adapter = new X402Adapter(requesterAddress, {
43
- * expectedNetwork: 'base-sepolia',
44
- * transferFn: async (to, amount) => {
45
- * const tx = await usdcContract.transfer(to, amount);
46
- * return tx.hash;
47
- * },
48
- * feeCollector: '0x...treasury', // Required: AGIRAILS fee recipient
49
- * });
34
+ * Hand-maintained because @x402/core exposes `Network` as a structural string
35
+ * pattern (`${string}:${string}`), not a canonical enum. Review on each
36
+ * @x402/evm upgrade to keep in sync with upstream scheme support.
37
+ */
38
+ const DEFAULT_EVM_NETWORKS = [
39
+ 'eip155:1', // Ethereum mainnet
40
+ 'eip155:8453', // Base mainnet
41
+ 'eip155:84532', // Base Sepolia
42
+ 'eip155:10', // Optimism
43
+ 'eip155:42161', // Arbitrum One
44
+ 'eip155:137', // Polygon
45
+ ];
46
+ /**
47
+ * Canonical USDC contract address per supported EVM network (CAIP-2 keys).
50
48
  *
51
- * const result = await adapter.pay({
52
- * to: 'https://api.provider.com/service',
53
- * amount: '10', // Hint only, actual amount from 402
54
- * });
49
+ * Source: Circle's official deployment list as of 2026-04. Keep in sync with
50
+ * `DEFAULT_EVM_NETWORKS`. Used as the default `allowedAssets` allowlist so
51
+ * the adapter refuses to sign payments in any other token.
55
52
  *
56
- * // That's it! No release() needed.
57
- * console.log(result.response?.status); // 200
58
- * console.log(result.releaseRequired); // false
59
- * ```
53
+ * Addresses stored lowercase so comparisons can use `.toLowerCase()` on
54
+ * server-provided asset addresses directly.
60
55
  */
61
- class X402Adapter extends BaseAdapter_1.BaseAdapter {
62
- /**
63
- * Creates a new X402Adapter instance.
64
- *
65
- * @param requesterAddress - The requester's Ethereum address
66
- * @param config - X402-specific configuration
67
- */
68
- constructor(requesterAddress, config) {
69
- super(requesterAddress);
56
+ const DEFAULT_USDC_BY_NETWORK = {
57
+ 'eip155:1': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // Ethereum USDC
58
+ 'eip155:8453': '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // Base USDC
59
+ 'eip155:84532': '0x036cbd53842c5426634e7929541ec2318f3dcf7e', // Base Sepolia USDC
60
+ 'eip155:10': '0x0b2c639c533813f4aa9d7837caf62653d097ff85', // Optimism USDC
61
+ 'eip155:42161': '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // Arbitrum USDC
62
+ 'eip155:137': '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', // Polygon USDC
63
+ };
64
+ // ============================================================================
65
+ // X402Adapter
66
+ // ============================================================================
67
+ class X402Adapter {
68
+ constructor(config) {
70
69
  this.config = config;
71
- /**
72
- * Adapter metadata - atomic, no escrow.
73
- */
74
70
  this.metadata = {
75
71
  id: 'x402',
76
- name: 'X402 Atomic Payment Adapter',
72
+ name: 'x402 v2',
77
73
  usesEscrow: false,
78
74
  supportsDisputes: false,
79
75
  requiresIdentity: false,
80
- settlementMode: 'atomic',
76
+ settlementMode: 'atomic', // x402 is stateless HTTP; settlement is atomic via facilitator
81
77
  priority: 70,
82
78
  };
83
- /** Local cache of payments for status lookups */
79
+ /** network:tokenAddress cached approved state */
80
+ this.permit2ApprovedCache = new Set();
81
+ /** network:tokenAddress → in-flight approve promise (coalesces concurrent callers) */
82
+ this.permit2InflightApprovals = new Map();
83
+ /** Completed payment records for getStatus() lookups. Capped to prevent unbounded growth. */
84
84
  this.payments = new Map();
85
- this.timeout = config.requestTimeout ?? 30000;
86
- this.fetchFn = config.fetchFn ?? fetch;
87
- this.defaultHeaders = config.defaultHeaders ?? {};
88
- this.transferFn = config.transferFn;
85
+ if (typeof config.walletProvider.signTypedData !== 'function') {
86
+ throw new X402Errors_1.X402ConfigError('X402Adapter requires a walletProvider with signTypedData() support. ' +
87
+ 'Both EOAWalletProvider and AutoWalletProvider implement this in @agirails/sdk@3.3.0+.');
88
+ }
89
+ const signer = this.walletProviderToClientEvmSigner(config.walletProvider);
90
+ const scheme = new evm_1.ExactEvmScheme(signer);
91
+ // I1: compute and cache allowed network list once — selectRequirements
92
+ // runs on every payment, so we must avoid re-resolving per-call.
93
+ this.allowedNetworks = resolveAllowedNetworks(config.allowedNetworks);
94
+ // P1-1: resolve allowed assets. Undefined → canonical USDC per network.
95
+ // Empty array → sentinel for "allow any asset" (explicit opt-out).
96
+ // Non-empty array → user-provided override.
97
+ if (config.allowedAssets === undefined) {
98
+ const defaults = this.allowedNetworks
99
+ .map((n) => DEFAULT_USDC_BY_NETWORK[n])
100
+ .filter((a) => typeof a === 'string');
101
+ this.allowedAssetsLc = new Set(defaults.map((a) => a.toLowerCase()));
102
+ }
103
+ else if (config.allowedAssets.length === 0) {
104
+ this.allowedAssetsLc = undefined; // sentinel: any asset
105
+ }
106
+ else {
107
+ this.allowedAssetsLc = new Set(config.allowedAssets.map((a) => a.toLowerCase()));
108
+ }
109
+ // P1-3: resolve allowed hosts. Default empty = always require opt-in.
110
+ this.allowedHostsLc = new Set((config.allowedHosts ?? []).map((h) => h.toLowerCase()));
111
+ // Build x402 client via fromConfig with all scheme registrations up front,
112
+ // plus our paymentRequirementsSelector. Hook is registered on the returned
113
+ // client instance (fromConfig does not take hooks).
114
+ this.x402 = fetch_1.x402Client.fromConfig({
115
+ schemes: this.allowedNetworks.map((network) => ({ network, client: scheme })),
116
+ paymentRequirementsSelector: this.selectRequirements.bind(this),
117
+ });
118
+ // onBeforePaymentCreation hook runs AFTER selectRequirements picks a
119
+ // requirement and BEFORE signing. We await Permit2 approve for Smart
120
+ // Wallet buyers inside the hook — single roundtrip, no race.
121
+ this.x402.onBeforePaymentCreation(this.beforePaymentCreationHook.bind(this));
122
+ // Wrap global fetch with the configured x402Client. This yields a fetch
123
+ // function that transparently retries on 402 with a signed payment payload.
124
+ this.fetchWithPayment = (0, fetch_1.wrapFetchWithPayment)(config.fetchImpl ?? fetch, this.x402);
125
+ // P1-3: hardened default cap lowered from $10 → $1. Reduces blast radius
126
+ // of accidental `client.pay({to: 'https://...'})` calls. Users who need
127
+ // higher caps must opt in explicitly via config.
128
+ this.maxAmountPerTx = parseUsdcAmount(config.maxAmountPerTx ?? '1');
129
+ this.maxAuthorizationValidSec = config.maxAuthorizationValidSec ?? 300;
89
130
  }
90
131
  // ==========================================================================
91
- // IAdapter Implementation
132
+ // IAdapter implementation
92
133
  // ==========================================================================
93
134
  /**
94
- * Check if this adapter can handle the given parameters.
135
+ * STRICT HTTPS ONLY. `http://` is rejected at the canHandle level to prevent
136
+ * MITM interception of signed payment payloads. Integration tests that need
137
+ * localhost use a dedicated test flag (not exposed to end users).
95
138
  *
96
- * X402Adapter handles HTTPS URLs only (security requirement).
139
+ * P1-3: canHandle returns true for any HTTPS URL so the router can select
140
+ * this adapter, but `validate()` enforces explicit opt-in before payment
141
+ * actually executes. This keeps the declarative "any HTTPS is x402-capable"
142
+ * shape while protecting against accidental auto-pay.
97
143
  */
98
144
  canHandle(params) {
99
- if (typeof params.to !== 'string') {
100
- return false;
101
- }
102
- try {
103
- const url = new URL(params.to);
104
- return url.protocol === 'https:';
105
- }
106
- catch {
107
- return false;
108
- }
145
+ return /^https:\/\//i.test(params.to);
109
146
  }
110
- /**
111
- * Validate parameters before execution.
112
- */
113
147
  validate(params) {
148
+ if (!params.to || typeof params.to !== 'string') {
149
+ throw new X402Errors_1.X402ConfigError('x402: params.to must be a non-empty string URL');
150
+ }
114
151
  if (!this.canHandle(params)) {
115
- throw new BaseAdapter_1.ValidationError(`X402 requires HTTPS URL, got: "${params.to}". ` +
116
- `HTTP endpoints are not supported for security reasons.`);
152
+ throw new X402Errors_1.X402ConfigError(`x402: refusing non-HTTPS target ${params.to}. Only https:// URLs are supported ` +
153
+ `to prevent MITM interception of signed payment payloads.`);
154
+ }
155
+ // P1-3: Explicit opt-in gate. A URL is allowed to trigger a payment only if:
156
+ // (a) caller set `metadata.paymentMethod === 'x402'`, OR
157
+ // (b) the target host is in the allowedHosts allowlist.
158
+ // Without one of these, throw so an accidental `client.pay({to: 'https://...'})`
159
+ // call doesn't silently cost money.
160
+ const explicitOptIn = params.metadata?.paymentMethod === 'x402';
161
+ let hostAllowed = false;
162
+ if (this.allowedHostsLc.size > 0) {
163
+ try {
164
+ const host = new URL(params.to).hostname.toLowerCase();
165
+ hostAllowed = this.allowedHostsLc.has(host);
166
+ }
167
+ catch {
168
+ // fall through — invalid URL will surface below
169
+ }
117
170
  }
118
- const url = new URL(params.to);
119
- if (url.username || url.password) {
120
- throw new BaseAdapter_1.ValidationError('URL cannot contain embedded credentials (username:password).');
171
+ if (!explicitOptIn && !hostAllowed) {
172
+ throw new X402Errors_1.X402ConfigError(`x402: refusing to auto-pay ${params.to}. HTTPS URLs trigger x402 payments ` +
173
+ `only when the caller explicitly opts in. Either:\n` +
174
+ ` (a) pass metadata: { paymentMethod: 'x402' } to client.pay(), or\n` +
175
+ ` (b) add the host to X402AdapterConfig.allowedHosts.\n` +
176
+ `This safeguard prevents accidental charges from unrelated HTTPS calls.`);
121
177
  }
122
178
  }
123
- /**
124
- * Execute atomic x402 payment flow with full HTTP support.
125
- *
126
- * 1. Request endpoint → get 402
127
- * 2. Parse payment headers
128
- * 3. Execute atomic USDC transfer
129
- * 4. Retry with tx hash as proof (same method/headers/body)
130
- * 5. Return response (settlement complete!)
131
- *
132
- * @param params - Payment parameters with optional HTTP method, headers, body
133
- */
134
179
  async pay(params) {
135
180
  this.validate(params);
136
- const endpoint = params.to;
137
- const x402Params = params;
138
- // Extract HTTP options
139
- const method = x402Params.method ?? 'GET';
140
- const requestHeaders = x402Params.headers ?? {};
141
- const requestBody = this.serializeBody(x402Params.body, x402Params.contentType);
142
- const contentType = x402Params.contentType ??
143
- (x402Params.body && method !== 'GET' ? 'application/json' : undefined);
144
- // Step 1: Initial request
145
- const initialResponse = await this.makeRequest(endpoint, method, requestHeaders, requestBody, contentType);
146
- // Step 2: Check response status
147
- if (initialResponse.status !== 402) {
148
- if (initialResponse.ok) {
149
- return this.createFreeServiceResult(params, initialResponse);
181
+ Logger_1.sdkLogger.debug('x402 payment attempt', { to: params.to });
182
+ let res;
183
+ try {
184
+ const method = params.httpMethod ?? 'GET';
185
+ const headers = { accept: 'application/json' };
186
+ if (params.httpHeaders) {
187
+ Object.assign(headers, params.httpHeaders);
150
188
  }
151
- throw new x402_1.X402Error(`Expected 402 Payment Required, got ${initialResponse.status}`, x402_1.X402ErrorCode.NOT_402_RESPONSE, initialResponse);
152
- }
153
- // Step 3: Parse payment headers
154
- const paymentHeaders = this.parsePaymentHeaders(initialResponse);
155
- // Step 4: Validate network
156
- if (paymentHeaders.network !== this.config.expectedNetwork) {
157
- throw new x402_1.X402Error(`Network mismatch: expected ${this.config.expectedNetwork}, got ${paymentHeaders.network}`, x402_1.X402ErrorCode.NETWORK_MISMATCH, initialResponse);
158
- }
159
- // Step 5: Validate deadline
160
- const now = Math.floor(Date.now() / 1000);
161
- if (paymentHeaders.deadline <= now) {
162
- throw new x402_1.X402Error(`Payment deadline has passed: ${new Date(paymentHeaders.deadline * 1000).toISOString()}`, x402_1.X402ErrorCode.DEADLINE_PASSED, initialResponse);
163
- }
164
- // Step 6: ATOMIC PAYMENT - via relay (with on-chain fee) or feeCollector (two transfers)
165
- const { txHash, feeBreakdown } = await this.executeAtomicPayment(paymentHeaders);
166
- // Step 7: Retry with proof (same method/headers/body + payment proof)
167
- const serviceResponse = await this.retryWithProof(endpoint, txHash, method, requestHeaders, requestBody, contentType);
168
- // Step 8: Cache payment record for status lookups
169
- this.payments.set(txHash, {
170
- txHash,
171
- provider: paymentHeaders.paymentAddress.toLowerCase(),
172
- requester: this.requesterAddress.toLowerCase(),
173
- amount: paymentHeaders.amount,
174
- timestamp: now,
175
- endpoint,
176
- feeBreakdown,
177
- });
178
- // Step 9: Return result - DONE! No release needed.
179
- return {
180
- txId: txHash,
181
- escrowId: null, // No escrow!
182
- adapter: this.metadata.id,
183
- state: 'COMMITTED', // Atomic = immediately settled
184
- success: true,
185
- amount: this.formatAmount(paymentHeaders.amount),
186
- response: serviceResponse,
187
- releaseRequired: false, // KEY DIFFERENCE from ACTP
188
- provider: paymentHeaders.paymentAddress.toLowerCase(),
189
- requester: this.requesterAddress.toLowerCase(),
190
- deadline: new Date(paymentHeaders.deadline * 1000).toISOString(),
191
- feeBreakdown,
192
- };
193
- }
194
- /**
195
- * Serialize request body to string.
196
- */
197
- serializeBody(body, _contentType) {
198
- if (body === undefined)
199
- return undefined;
200
- if (typeof body === 'string')
201
- return body;
202
- return JSON.stringify(body);
189
+ if (params.httpBody && typeof params.httpBody === 'string' && !headers['content-type'] && !headers['Content-Type']) {
190
+ headers['content-type'] = 'application/json';
191
+ }
192
+ const fetchInit = { method, headers };
193
+ if (params.httpBody && method !== 'GET' && method !== 'DELETE') {
194
+ fetchInit.body = params.httpBody;
195
+ }
196
+ res = await this.fetchWithPayment(params.to, fetchInit);
197
+ }
198
+ catch (e) {
199
+ // Hook aborts bubble up here as "Payment creation aborted: {reason}"
200
+ if (e instanceof Error && /aborted/i.test(e.message)) {
201
+ throw new X402Errors_1.X402PaymentFailedError(`x402 payment aborted before signing: ${e.message}`);
202
+ }
203
+ throw new X402Errors_1.X402PaymentFailedError(`x402 payment failed: ${e instanceof Error ? e.message : String(e)}`);
204
+ }
205
+ if (!res.ok) {
206
+ throw new X402Errors_1.X402PaymentFailedError(`x402 payment returned HTTP ${res.status} ${res.statusText}`);
207
+ }
208
+ const responseHeader = res.headers.get('payment-response');
209
+ return await this.mapToPayResult(res, responseHeader, params);
203
210
  }
204
- /**
205
- * Get payment status by transaction hash.
206
- *
207
- * For atomic payments, status is simple:
208
- * - If tx exists → SETTLED (atomic = instant settlement)
209
- */
210
211
  async getStatus(txId) {
211
212
  const record = this.payments.get(txId);
212
213
  if (!record) {
213
- throw new Error(`Payment ${txId} not found. X402 payments are atomic and stateless.`);
214
+ throw new Error(`x402 payment ${txId} not found. x402 payments are atomic and stateless; ` +
215
+ `only payments made through this adapter instance are tracked.`);
214
216
  }
217
+ // B4: state consistency — pay() returns `UnifiedPayResult.state = 'COMMITTED'`
218
+ // (the type only allows 'COMMITTED' | 'IN_PROGRESS'), so getStatus() must
219
+ // also return 'COMMITTED' for the same tx. Despite the name, for x402 this
220
+ // COMMITTED is effectively SETTLED — facilitator has confirmed on-chain
221
+ // settlement by the time mapToPayResult writes the record here, so there's
222
+ // no work-in-progress phase to worry about. Callers should NOT interpret
223
+ // 'COMMITTED' on an x402 record as "waiting for provider" — release is
224
+ // not required and lifecycle methods throw.
215
225
  return {
216
- state: 'SETTLED',
226
+ state: 'COMMITTED',
217
227
  canStartWork: false,
218
228
  canDeliver: false,
219
229
  canRelease: false,
220
230
  canDispute: false,
221
- amount: this.formatAmount(record.amount),
222
- provider: record.provider,
223
- requester: record.requester,
231
+ amount: formatUsdcAmount(record.amount),
232
+ provider: record.payTo,
233
+ requester: record.payer,
224
234
  };
225
235
  }
226
- /**
227
- * Not applicable for atomic payments.
228
- * @throws {Error} Always - x402 has no lifecycle
229
- */
230
236
  async startWork(_txId) {
231
- throw new Error('X402 is atomic - no lifecycle methods. ' +
232
- 'Payment and delivery happen atomically. Use ACTP for stateful transactions.');
237
+ throw new Error('x402 is stateless no lifecycle methods. ' +
238
+ 'The HTTP response IS the delivery. Use ACTP adapters for stateful transactions.');
233
239
  }
234
- /**
235
- * Not applicable for atomic payments.
236
- * @throws {Error} Always - x402 has no lifecycle
237
- */
238
240
  async deliver(_txId, _proof) {
239
- throw new Error('X402 is atomic - no lifecycle methods. ' +
240
- 'The HTTP response IS the delivery. Use ACTP for stateful transactions.');
241
+ throw new Error('x402 is stateless no lifecycle methods. ' +
242
+ 'The HTTP response IS the delivery. Use ACTP adapters for stateful transactions.');
241
243
  }
242
- /**
243
- * Not applicable for atomic payments.
244
- * @throws {Error} Always - x402 has no escrow
245
- */
246
244
  async release(_escrowId, _attestationUID) {
247
- throw new Error('X402 is atomic - no escrow to release. ' +
248
- 'Payment settled instantly. Use ACTP for escrow-based transactions.');
245
+ throw new Error('x402 has no escrow to release — payment settles instantly via the facilitator. ' +
246
+ 'Use ACTP adapters for escrow-based transactions.');
249
247
  }
250
248
  // ==========================================================================
251
- // Private Helpers
249
+ // @x402 hook + selector
252
250
  // ==========================================================================
253
251
  /**
254
- * Make an HTTP request with full options support.
252
+ * Registered as `onBeforePaymentCreation` hook on the x402Client in the
253
+ * constructor. Runs AFTER selectRequirements has chosen a target and BEFORE
254
+ * the scheme client signs the payload.
255
+ *
256
+ * For Smart Wallet + Permit2 flow, this is where we ensure the one-time
257
+ * Permit2 approve has been submitted and confirmed. The hook is awaited by
258
+ * @x402/core's createPaymentPayload, so we can block signing until the
259
+ * approve lands — no double roundtrip, no race.
255
260
  *
256
- * @param url - Request URL
257
- * @param method - HTTP method
258
- * @param customHeaders - Custom headers from request params
259
- * @param body - Request body (optional)
260
- * @param contentType - Content-Type header (optional)
261
- * @param proofHeaders - Payment proof headers for retry (optional)
261
+ * Return `{ abort: true, reason }` to cancel the payment cleanly.
262
262
  */
263
- async makeRequest(url, method, customHeaders = {}, body, contentType, proofHeaders) {
264
- const controller = new AbortController();
265
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
266
- try {
267
- // Build headers: defaults + custom + content-type + proof
268
- const headers = new Headers(this.defaultHeaders);
269
- // Add custom headers from request
270
- for (const [key, value] of Object.entries(customHeaders)) {
271
- headers.set(key, value);
272
- }
273
- // Add content-type if provided
274
- if (contentType) {
275
- headers.set('Content-Type', contentType);
276
- }
277
- // Add proof headers for retry
278
- if (proofHeaders) {
279
- for (const [key, value] of Object.entries(proofHeaders)) {
280
- headers.set(key, value);
281
- }
282
- }
283
- const init = {
284
- method,
285
- headers,
286
- signal: controller.signal,
287
- };
288
- // Add body for non-GET requests
289
- if (body && method !== 'GET') {
290
- init.body = body;
263
+ async beforePaymentCreationHook(ctx) {
264
+ const walletInfo = this.config.walletProvider.getWalletInfo();
265
+ if (walletInfo.tier !== 'auto')
266
+ return; // EOA doesn't need Permit2 approve
267
+ if (this.config.autoApprovePermit2 === false)
268
+ return;
269
+ const reqs = ctx.selectedRequirements;
270
+ const method = reqs.extra
271
+ ?.assetTransferMethod;
272
+ if (method !== 'permit2')
273
+ return; // EIP-3009 path doesn't need approve
274
+ // Undeployed Smart Wallets (counterfactual): skip Permit2 approve.
275
+ // The facilitator with deployERC4337WithEIP6492 handles deployment +
276
+ // Permit2 settlement atomically. Attempting approve on an undeployed
277
+ // contract wallet would fail (no code → paymaster rejects UserOp).
278
+ // The on-chain allowance check returns false for undeployed wallets
279
+ // (no code to call), so we detect this via getCode-like heuristic.
280
+ const alreadyApproved = await this.readPermit2AllowanceIsSet(reqs.asset);
281
+ if (!alreadyApproved) {
282
+ // Check if wallet is deployed — if not, skip approve entirely
283
+ const isDeployed = await this.isWalletDeployed();
284
+ if (!isDeployed) {
285
+ Logger_1.sdkLogger.debug('x402: Permit2 approve skipped — Smart Wallet not yet deployed (ERC-6492 facilitator handles atomically)');
286
+ return;
291
287
  }
292
- return await this.fetchFn(url, init);
293
- }
294
- finally {
295
- clearTimeout(timeoutId);
296
288
  }
297
- }
298
- /**
299
- * Parse X-Payment-* headers from 402 response.
300
- */
301
- parsePaymentHeaders(response) {
302
- const h = response.headers;
303
- const requiredHeader = h.get(x402_1.X402_HEADERS.REQUIRED);
304
- if (requiredHeader?.toLowerCase() !== 'true') {
305
- throw new x402_1.X402Error(`Missing or invalid ${x402_1.X402_HEADERS.REQUIRED} header`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
306
- }
307
- const address = h.get(x402_1.X402_HEADERS.ADDRESS);
308
- const amount = h.get(x402_1.X402_HEADERS.AMOUNT);
309
- const network = h.get(x402_1.X402_HEADERS.NETWORK);
310
- const token = h.get(x402_1.X402_HEADERS.TOKEN);
311
- const deadline = h.get(x402_1.X402_HEADERS.DEADLINE);
312
- if (!address) {
313
- throw new x402_1.X402Error(`Missing ${x402_1.X402_HEADERS.ADDRESS}`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
314
- }
315
- if (!amount) {
316
- throw new x402_1.X402Error(`Missing ${x402_1.X402_HEADERS.AMOUNT}`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
317
- }
318
- if (!network) {
319
- throw new x402_1.X402Error(`Missing ${x402_1.X402_HEADERS.NETWORK}`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
320
- }
321
- if (!token) {
322
- throw new x402_1.X402Error(`Missing ${x402_1.X402_HEADERS.TOKEN}`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
323
- }
324
- if (!deadline) {
325
- throw new x402_1.X402Error(`Missing ${x402_1.X402_HEADERS.DEADLINE}`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
326
- }
327
- // Validate address
328
- const validatedAddress = this.validatePaymentAddress(address, response);
329
- // Validate amount
330
- if (!/^\d+$/.test(amount)) {
331
- throw new x402_1.X402Error(`Invalid ${x402_1.X402_HEADERS.AMOUNT}: "${amount}"`, x402_1.X402ErrorCode.INVALID_AMOUNT, response);
332
- }
333
- // Validate network
334
- if (!(0, x402_1.isValidX402Network)(network)) {
335
- throw new x402_1.X402Error(`Invalid ${x402_1.X402_HEADERS.NETWORK}: "${network}"`, x402_1.X402ErrorCode.INVALID_NETWORK, response);
336
- }
337
- // Validate token
338
- if (token.toUpperCase() !== 'USDC') {
339
- throw new x402_1.X402Error(`Unsupported token: "${token}". Only USDC supported.`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
340
- }
341
- const deadlineNum = parseInt(deadline, 10);
342
- if (isNaN(deadlineNum) || deadlineNum <= 0) {
343
- throw new x402_1.X402Error(`Invalid ${x402_1.X402_HEADERS.DEADLINE}: "${deadline}"`, x402_1.X402ErrorCode.MISSING_HEADERS, response);
289
+ else {
290
+ // Already approved — no action needed
291
+ return;
344
292
  }
345
- return {
346
- required: true,
347
- paymentAddress: validatedAddress,
348
- amount,
349
- network,
350
- token: 'USDC',
351
- deadline: deadlineNum,
352
- serviceId: h.get(x402_1.X402_HEADERS.SERVICE_ID) ?? undefined,
353
- };
354
- }
355
- /**
356
- * Validate payment address from header.
357
- */
358
- validatePaymentAddress(address, response) {
359
293
  try {
360
- return this.validateAddress(address, x402_1.X402_HEADERS.ADDRESS);
294
+ await this.ensurePermit2Approved(reqs.network, reqs.asset);
361
295
  }
362
- catch {
363
- throw new x402_1.X402Error(`Invalid ${x402_1.X402_HEADERS.ADDRESS}: "${address}"`, x402_1.X402ErrorCode.INVALID_ADDRESS, response);
296
+ catch (e) {
297
+ const msg = e instanceof Error ? e.message : String(e);
298
+ Logger_1.sdkLogger.warn('x402 Permit2 approve failed, aborting payment', { error: msg });
299
+ return {
300
+ abort: true,
301
+ reason: msg,
302
+ };
364
303
  }
365
304
  }
366
305
  /**
367
- * Execute atomic payment with fee splitting.
306
+ * Registered as `paymentRequirementsSelector` on the x402Client.
307
+ * Called after server's payment-required is parsed, before signing.
368
308
  *
369
- * Priority: relay (atomic, 1 tx) > feeCollector (2 tx) > error
370
- * Relay flow: approve relay relay.payWithFee(provider, gross, serviceId)
371
- * feeCollector flow: transferFn(provider, net) + transferFn(feeCollector, fee)
309
+ * Picks the best requirement from `accepts[]` based on:
310
+ * 1. scheme === "exact" AND network in our allowlist
311
+ * 2. Wallet tier: Smart Wallet prefers Permit2, EOA prefers EIP-3009
312
+ * 3. Amount within maxAmountPerTx safety cap
313
+ * 4. Clamps maxTimeoutSeconds to maxAuthorizationValidSec (MEV cap)
372
314
  */
373
- async executeAtomicPayment(headers) {
374
- try {
375
- // Relay path: on-chain fee splitting (atomic, preferred)
376
- if (this.config.relayAddress && this.config.approveFn && this.config.relayPayFn) {
377
- const grossAmount = headers.amount;
378
- const feeBps = this.config.platformFeeBps ?? 100;
379
- const MIN_FEE = 50000n; // $0.05 USDC
380
- // Calculate fee: max(gross * bps / 10000, MIN_FEE)
381
- const grossBig = BigInt(grossAmount);
382
- const bpsFee = (grossBig * BigInt(feeBps)) / 10000n;
383
- const fee = bpsFee > MIN_FEE ? bpsFee : MIN_FEE;
384
- const providerNet = grossBig - fee;
385
- // 1. Approve relay for gross amount
386
- await this.config.approveFn(this.config.relayAddress, grossAmount);
387
- // 2. Call relay.payWithFee
388
- const serviceId = headers.serviceId ?? '0x' + '0'.repeat(64);
389
- const txHash = await this.config.relayPayFn(headers.paymentAddress, grossAmount, serviceId);
390
- return {
391
- txHash,
392
- feeBreakdown: {
393
- grossAmount,
394
- providerNet: providerNet.toString(),
395
- platformFee: fee.toString(),
396
- feeBps,
397
- estimated: true,
398
- },
399
- };
400
- }
401
- // feeCollector path: client-side fee splitting (non-atomic fallback)
402
- if (!this.config.feeCollector) {
403
- throw new x402_1.X402Error('x402 payment requires fee collection: configure either relayAddress (preferred) or feeCollector. ' +
404
- 'Since v2.6.0, transferFn-only configs are no longer allowed to prevent zero-fee payments.', x402_1.X402ErrorCode.PAYMENT_FAILED);
405
- }
406
- const grossAmount = headers.amount;
407
- const feeBps = this.config.platformFeeBps ?? 100;
408
- const MIN_FEE = 50000n; // $0.05 USDC
409
- const grossBig = BigInt(grossAmount);
410
- // Guard: grossAmount must cover at least the minimum fee
411
- if (grossBig <= MIN_FEE) {
412
- throw new x402_1.X402Error(`Payment amount ${grossAmount} too small: must exceed minimum fee of ${MIN_FEE} ($0.05 USDC)`, x402_1.X402ErrorCode.PAYMENT_FAILED);
315
+ selectRequirements(_version, requirements) {
316
+ const allowed = this.allowedNetworks; // I1: cached
317
+ // P1-1: filter by scheme + network + asset. Asset check rejects any
318
+ // token that's not in our allowlist (canonical USDC per chain by default).
319
+ // Without this, a malicious or misconfigured server could advertise
320
+ // USDT/DAI/scam-token and we'd sign a payment with wrong decimals,
321
+ // bypassing the maxAmountPerTx cap silently.
322
+ const candidates = requirements.filter((r) => {
323
+ if (r.scheme !== 'exact')
324
+ return false;
325
+ if (!allowed.includes(r.network))
326
+ return false;
327
+ if (this.allowedAssetsLc) {
328
+ if (typeof r.asset !== 'string' || !this.allowedAssetsLc.has(r.asset.toLowerCase()))
329
+ return false;
413
330
  }
414
- const bpsFee = (grossBig * BigInt(feeBps)) / 10000n;
415
- const fee = bpsFee > MIN_FEE ? bpsFee : MIN_FEE;
416
- const providerNet = grossBig - fee;
417
- // 1. Transfer net amount to provider
418
- // NOTE: If transferFn returns a bare hash string (no { hash, success } object),
419
- // we treat it as success. Integrators SHOULD return { hash, success } to enable
420
- // detection of reverted/dropped transactions.
421
- const transferResult = await this.transferFn(headers.paymentAddress, providerNet.toString());
422
- const txHash = typeof transferResult === 'string'
423
- ? transferResult
424
- : transferResult.hash;
425
- const transferSuccess = typeof transferResult === 'string'
426
- ? true
427
- : transferResult.success !== false;
428
- if (!transferSuccess) {
429
- throw new Error(`transferFn returned unsuccessful receipt for tx ${txHash}`);
430
- }
431
- // 2. Transfer fee to AGIRAILS treasury (fail-closed).
432
- // IMPORTANT: Provider is already paid at this point. If fee transfer fails,
433
- // we throw PROVIDER_PAID_FEE_FAILED (NOT PAYMENT_FAILED) so callers know
434
- // NOT to retry retrying would double-pay the provider.
331
+ return true;
332
+ });
333
+ if (candidates.length === 0) {
334
+ const seen = requirements
335
+ .map((r) => `${r.scheme}@${r.network}(${(r.asset ?? '').slice(0, 10)}...)`)
336
+ .join(', ');
337
+ const assetInfo = this.allowedAssetsLc
338
+ ? `, allowed assets: [${[...this.allowedAssetsLc].map((a) => a.slice(0, 10) + '...').join(', ')}]`
339
+ : '';
340
+ throw new X402Errors_1.X402NetworkNotAllowedError(`x402: no accepted requirement. Server offered [${seen}], ` +
341
+ `allowed networks: [${allowed.join(', ')}]${assetInfo}.`);
342
+ }
343
+ // Wallet-tier-aware ordering
344
+ const walletInfo = this.config.walletProvider.getWalletInfo();
345
+ const isPermit2 = (r) => r.extra?.assetTransferMethod === 'permit2';
346
+ // Smart Wallet prefers Permit2 (EIP-3009 won't validate on contract addresses).
347
+ // EOA prefers EIP-3009 (simpler, no one-time approve needed).
348
+ const prioritized = walletInfo.tier === 'auto'
349
+ ? [...candidates].sort((a, b) => Number(isPermit2(b)) - Number(isPermit2(a)))
350
+ : [...candidates].sort((a, b) => Number(isPermit2(a)) - Number(isPermit2(b)));
351
+ // Note: we do NOT inject assetTransferMethod into requirements here.
352
+ // @x402/evm ExactEvmScheme client auto-selects EIP-3009 vs Permit2
353
+ // based on the signer type (EOA vs contract wallet). Injecting it
354
+ // would cause a requirements mismatch between buyer and server,
355
+ // breaking the facilitator's verify check.
356
+ const chosen = prioritized[0];
357
+ const amountBig = BigInt(chosen.amount);
358
+ if (amountBig > this.maxAmountPerTx) {
359
+ throw new X402Errors_1.X402AmountExceededError(`x402: required amount ${chosen.amount} (${formatUsdcAmount(amountBig)} USD) ` +
360
+ `exceeds maxAmountPerTx ${this.maxAmountPerTx.toString()} ` +
361
+ `(${this.config.maxAmountPerTx ?? '1'} USD).`);
362
+ }
363
+ // MEV hard cap on authorization validity
364
+ const serverTimeout = chosen.maxTimeoutSeconds ?? this.maxAuthorizationValidSec;
365
+ return {
366
+ ...chosen,
367
+ maxTimeoutSeconds: Math.min(serverTimeout, this.maxAuthorizationValidSec),
368
+ };
369
+ }
370
+ // ==========================================================================
371
+ // Permit2 approve (lazy, one-time, coalesced)
372
+ // ==========================================================================
373
+ async ensurePermit2Approved(network, token) {
374
+ const key = `${network}:${token.toLowerCase()}`;
375
+ if (this.permit2ApprovedCache.has(key))
376
+ return;
377
+ // Coalesce concurrent calls for the same (network, token).
378
+ // B1 fix: register the inflight promise BEFORE the IIFE's first await
379
+ // so concurrent callers that arrive mid-construction see it and wait
380
+ // instead of launching a duplicate approve.
381
+ const inflight = this.permit2InflightApprovals.get(key);
382
+ if (inflight)
383
+ return await inflight;
384
+ const doApprove = async () => {
435
385
  try {
436
- const feeResult = await this.transferFn(this.config.feeCollector, fee.toString());
437
- const feeSuccess = typeof feeResult === 'string'
438
- ? true
439
- : feeResult.success !== false;
440
- if (!feeSuccess) {
441
- throw new x402_1.X402Error(`Fee transfer failed after provider payment tx ${txHash}. DO NOT RETRY — provider already paid.`, x402_1.X402ErrorCode.PROVIDER_PAID_FEE_FAILED, undefined, { providerPaidTxHash: txHash });
386
+ // P1-2: check on-chain allowance BEFORE sending approve. The in-memory
387
+ // `permit2ApprovedCache` is only a fast path — after a process restart
388
+ // or horizontal scale, the cache is empty but the on-chain allowance
389
+ // may already be set from a prior run. Without this check we'd pay
390
+ // for the approve again.
391
+ const alreadyApproved = await this.readPermit2AllowanceIsSet(token);
392
+ if (alreadyApproved) {
393
+ this.permit2ApprovedCache.add(key);
394
+ Logger_1.sdkLogger.debug('x402 Permit2 approve: allowance already set on-chain, skipping', {
395
+ network,
396
+ token,
397
+ });
398
+ return;
399
+ }
400
+ Logger_1.sdkLogger.info('x402 Permit2 approve: submitting one-time USDC approve', {
401
+ network,
402
+ token,
403
+ });
404
+ // Verified v4.2 spike: createPermit2ApprovalTx takes positional
405
+ // tokenAddress (0x${string}), returns { to, data, value? }.
406
+ const approvalTx = (0, evm_1.createPermit2ApprovalTx)(token);
407
+ const receipt = await this.config.walletProvider.sendTransaction({
408
+ to: approvalTx.to,
409
+ data: approvalTx.data,
410
+ value: '0',
411
+ });
412
+ if (receipt && typeof receipt === 'object' && 'success' in receipt && !receipt.success) {
413
+ throw new X402Errors_1.X402ApprovalFailedError(`Permit2 approve transaction reverted on-chain for ${network}:${token}`);
442
414
  }
415
+ this.permit2ApprovedCache.add(key);
416
+ Logger_1.sdkLogger.info('x402 Permit2 approve confirmed', { network, token });
443
417
  }
444
- catch (error) {
445
- if (error instanceof x402_1.X402Error)
446
- throw error;
447
- throw new x402_1.X402Error(`Fee transfer failed after provider payment tx ${txHash}. DO NOT RETRY — provider already paid.`, x402_1.X402ErrorCode.PROVIDER_PAID_FEE_FAILED, undefined, { providerPaidTxHash: txHash });
418
+ catch (e) {
419
+ if (e instanceof X402Errors_1.X402ApprovalFailedError)
420
+ throw e; // don't double-wrap
421
+ if ((0, X402Errors_1.isPaymasterGateError)(e)) {
422
+ throw new X402Errors_1.X402PublishRequiredError();
423
+ }
424
+ throw new X402Errors_1.X402ApprovalFailedError(`Permit2 approve failed for ${network}:${token}: ${e instanceof Error ? e.message : String(e)}`);
448
425
  }
449
- return {
450
- txHash,
451
- feeBreakdown: {
452
- grossAmount,
453
- // providerNet is always gross - fee (that's what was transferred)
454
- providerNet: providerNet.toString(),
455
- // platformFee reflects the fee that was collected when payment succeeds.
456
- platformFee: fee.toString(),
457
- feeBps,
458
- estimated: false,
459
- },
460
- };
461
- }
462
- catch (error) {
463
- if (error instanceof x402_1.X402Error)
464
- throw error;
465
- throw new x402_1.X402Error(`Atomic payment failed: ${error instanceof Error ? error.message : 'Unknown error'}`, x402_1.X402ErrorCode.PAYMENT_FAILED);
466
- }
426
+ finally {
427
+ this.permit2InflightApprovals.delete(key);
428
+ }
429
+ };
430
+ // Register BEFORE invoking Promise constructor is synchronous so any
431
+ // concurrent caller that polls `.get(key)` after this line sees it.
432
+ const approvalPromise = doApprove();
433
+ this.permit2InflightApprovals.set(key, approvalPromise);
434
+ return await approvalPromise;
467
435
  }
468
436
  /**
469
- * Retry request with payment proof (tx hash).
470
- * Uses the same HTTP method, headers, and body as the original request.
437
+ * P1-2: Read `USDC.allowance(smartWallet, PERMIT2_ADDRESS)` via the wallet
438
+ * provider's read provider and return true if already >= a sensible threshold
439
+ * (half of max uint256 — Permit2 approves are typically MAX_UINT256).
440
+ *
441
+ * Returns false if the wallet provider doesn't expose a read provider
442
+ * (older wallet implementations) — callers then fall back to submitting
443
+ * the approve unconditionally, which is the safe (but slightly wasteful)
444
+ * default.
471
445
  *
472
- * @param endpoint - Request URL
473
- * @param txHash - Payment transaction hash as proof
474
- * @param method - Original HTTP method
475
- * @param customHeaders - Original custom headers
476
- * @param body - Original request body
477
- * @param contentType - Original content-type
446
+ * Uses eth_call with the standard ERC-20 allowance ABI selector to avoid
447
+ * pulling in additional contract ABIs.
478
448
  */
479
- async retryWithProof(endpoint, txHash, method = 'GET', customHeaders = {}, body, contentType) {
480
- // Add payment proof headers
481
- const proofHeaders = {
482
- [x402_1.X402_PROOF_HEADERS.TX_ID]: txHash,
483
- // No escrow ID for atomic payments
484
- };
485
- const response = await this.makeRequest(endpoint, method, customHeaders, body, contentType, proofHeaders);
486
- if (!response.ok) {
487
- throw new x402_1.X402Error(`Retry failed: ${response.status} ${response.statusText}`, x402_1.X402ErrorCode.RETRY_FAILED, response);
449
+ async readPermit2AllowanceIsSet(token) {
450
+ if (typeof this.config.walletProvider.getReadProvider !== 'function') {
451
+ return false; // no read capability — submit approve unconditionally
452
+ }
453
+ const rawProvider = this.config.walletProvider.getReadProvider();
454
+ // Duck-type: ethers v6 provider has `.call({to, data}): Promise<string>`.
455
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
456
+ const providerAny = rawProvider;
457
+ if (!providerAny || typeof providerAny.call !== 'function')
458
+ return false;
459
+ // ERC-20 allowance(address owner, address spender) — selector 0xdd62ed3e
460
+ const owner = this.config.walletProvider.getAddress().toLowerCase().replace(/^0x/, '');
461
+ const spender = evm_1.PERMIT2_ADDRESS.toLowerCase().replace(/^0x/, '');
462
+ const data = '0xdd62ed3e' +
463
+ owner.padStart(64, '0') +
464
+ spender.padStart(64, '0');
465
+ try {
466
+ const result = await providerAny.call({ to: token, data });
467
+ if (!result || result === '0x')
468
+ return false;
469
+ const allowance = BigInt(result);
470
+ // Permit2 approve is typically MAX_UINT256. Treat any value above
471
+ // half-max as "already approved" to tolerate partial-spend scenarios.
472
+ const THRESHOLD = (1n << 255n);
473
+ return allowance >= THRESHOLD;
474
+ }
475
+ catch (e) {
476
+ Logger_1.sdkLogger.debug('x402 allowance read failed, falling back to submit', {
477
+ error: e instanceof Error ? e.message : String(e),
478
+ });
479
+ return false;
488
480
  }
489
- return response;
490
481
  }
491
482
  /**
492
- * Create result for free services (200 on initial request).
483
+ * Check if the Smart Wallet is deployed on-chain (has code).
484
+ * Returns true for EOA wallets (they're always "deployed" in a loose sense)
485
+ * and for deployed Smart Wallets. Returns false for counterfactual wallets.
493
486
  */
494
- createFreeServiceResult(params, response) {
487
+ async isWalletDeployed() {
488
+ if (typeof this.config.walletProvider.getReadProvider !== 'function') {
489
+ return true; // can't check — assume deployed (safe default: attempt approve)
490
+ }
491
+ const rawProvider = this.config.walletProvider.getReadProvider();
492
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
493
+ const providerAny = rawProvider;
494
+ if (!providerAny || typeof providerAny.getCode !== 'function')
495
+ return true;
496
+ try {
497
+ const code = await providerAny.getCode(this.config.walletProvider.getAddress());
498
+ return code !== '0x' && code !== null && code !== undefined && code.length > 2;
499
+ }
500
+ catch {
501
+ return true; // can't check — assume deployed
502
+ }
503
+ }
504
+ // ==========================================================================
505
+ // Response mapping
506
+ // ==========================================================================
507
+ async mapToPayResult(res, paymentResponseHeader, params) {
508
+ // FIX v4.1: missing payment-response header is NOT silent success.
509
+ // x402 spec: facilitator sets this header ONLY after on-chain settlement
510
+ // is confirmed. Without it we have no settlement proof — reorg, pending
511
+ // mempool, facilitator bug, or malicious server could all be causes.
512
+ if (!paymentResponseHeader) {
513
+ throw new X402Errors_1.X402SettlementProofMissingError();
514
+ }
515
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
516
+ let decoded;
517
+ try {
518
+ decoded = (0, fetch_1.decodePaymentResponseHeader)(paymentResponseHeader);
519
+ }
520
+ catch (e) {
521
+ throw new X402Errors_1.X402SettlementProofMissingError(`Failed to decode payment-response header: ${e instanceof Error ? e.message : String(e)}`);
522
+ }
523
+ const rawTxHash = decoded?.transaction;
524
+ const rawNetwork = decoded?.network;
525
+ const rawPayer = decoded?.payer;
526
+ const payTo = decoded?.payTo;
527
+ const amount = decoded?.amount;
528
+ // P1: Validate critical settlement proof fields instead of falling back
529
+ // to empty values. Without a valid tx hash we have no on-chain proof.
530
+ const missing = [];
531
+ if (!rawTxHash || !/^0x[0-9a-f]{64}$/i.test(rawTxHash))
532
+ missing.push('transaction');
533
+ if (!rawNetwork)
534
+ missing.push('network');
535
+ if (!rawPayer || !/^0x[0-9a-f]{40}$/i.test(rawPayer))
536
+ missing.push('payer');
537
+ // payTo is optional in x402 spec — Coinbase facilitator omits it
538
+ if (missing.length > 0) {
539
+ throw new X402Errors_1.X402SettlementProofMissingError(`payment-response header decoded but missing/invalid fields: ${missing.join(', ')}. ` +
540
+ `Decoded values: transaction=${rawTxHash ?? 'undefined'}, network=${rawNetwork ?? 'undefined'}, ` +
541
+ `payer=${rawPayer ?? 'undefined'}. Do not treat as settled.`);
542
+ }
543
+ // After validation gate, these are guaranteed non-undefined.
544
+ const txHash = rawTxHash;
545
+ const network = rawNetwork;
546
+ const payer = rawPayer;
547
+ // Replay detection: payer must match our wallet address
548
+ const ourAddress = this.config.walletProvider.getAddress().toLowerCase();
549
+ if (payer.toLowerCase() !== ourAddress) {
550
+ throw new X402Errors_1.X402SettlementProofMissingError(`payment-response payer ${payer} does not match our wallet ${ourAddress}. ` +
551
+ `Possible replay of another client's settlement.`);
552
+ }
553
+ const amountBig = safeBigInt(amount ?? '0');
554
+ this.payments.set(txHash, {
555
+ txId: txHash,
556
+ amount: amountBig,
557
+ network,
558
+ payer,
559
+ payTo: payTo ?? '',
560
+ settledAt: Date.now(),
561
+ });
562
+ // Evict oldest entry if over cap to prevent unbounded memory growth
563
+ if (this.payments.size > X402Adapter.MAX_PAYMENT_RECORDS) {
564
+ const oldest = this.payments.keys().next().value;
565
+ if (oldest)
566
+ this.payments.delete(oldest);
567
+ }
568
+ Logger_1.sdkLogger.info('x402 settlement confirmed', {
569
+ txHash,
570
+ network,
571
+ amount: String(amount ?? '0'),
572
+ });
495
573
  return {
496
- txId: '0x' + '0'.repeat(64),
574
+ txId: txHash,
497
575
  escrowId: null,
498
- adapter: this.metadata.id,
576
+ adapter: 'x402',
499
577
  state: 'COMMITTED',
500
578
  success: true,
501
- amount: '0.00 USDC',
502
- response,
579
+ amount: formatUsdcAmount(amountBig),
580
+ response: res,
503
581
  releaseRequired: false,
504
- provider: '0x' + '0'.repeat(40),
505
- requester: this.requesterAddress.toLowerCase(),
506
- deadline: new Date(Date.now() + 86400000).toISOString(),
582
+ provider: payTo || params.to,
583
+ requester: payer,
584
+ deadline: new Date().toISOString(), // x402 is atomic — settled at return time, no future deadline
585
+ erc8004AgentId: params.erc8004AgentId,
586
+ };
587
+ }
588
+ // ==========================================================================
589
+ // Helpers
590
+ // ==========================================================================
591
+ /**
592
+ * Bridge our IWalletProvider.signTypedData to @x402/evm's ClientEvmSigner
593
+ * structural type. Only `address` + `signTypedData` are required for the
594
+ * `exact` scheme flow we use.
595
+ *
596
+ * B3: no outer try/catch here — each IWalletProvider implementation is the
597
+ * error-converting boundary and already throws X402SignatureFailedError on
598
+ * failure. Wrapping here would produce double-nested error messages like
599
+ * "walletProvider.signTypedData failed: EOA signTypedData failed: ...".
600
+ */
601
+ walletProviderToClientEvmSigner(wp) {
602
+ return {
603
+ address: wp.getAddress(),
604
+ async signTypedData(params) {
605
+ const typedData = {
606
+ domain: params.domain,
607
+ types: params.types,
608
+ primaryType: params.primaryType,
609
+ message: params.message,
610
+ };
611
+ const sig = await wp.signTypedData(typedData);
612
+ return sig;
613
+ },
507
614
  };
508
615
  }
509
616
  }
510
617
  exports.X402Adapter = X402Adapter;
618
+ X402Adapter.MAX_PAYMENT_RECORDS = 10000;
619
+ // ============================================================================
620
+ // Local helpers
621
+ // ============================================================================
622
+ /**
623
+ * Parse human-readable USD amount like "10" or "0.50" to USDC 6-decimal bigint.
624
+ */
625
+ function parseUsdcAmount(usd) {
626
+ const trimmed = usd.trim().replace(/^\$/, '');
627
+ if (!/^\d+(\.\d{1,6})?$/.test(trimmed)) {
628
+ throw new X402Errors_1.X402ConfigError(`Invalid maxAmountPerTx "${usd}" — must be a non-negative decimal with at most 6 digits after the point.`);
629
+ }
630
+ const [whole, frac = ''] = trimmed.split('.');
631
+ const fracPadded = (frac + '000000').slice(0, 6);
632
+ return BigInt(whole + fracPadded);
633
+ }
634
+ /**
635
+ * Format USDC 6-decimal bigint back to human-readable USD string.
636
+ */
637
+ function formatUsdcAmount(amount) {
638
+ const whole = amount / 1000000n;
639
+ const frac = amount % 1000000n;
640
+ if (frac === 0n)
641
+ return whole.toString();
642
+ const fracStr = frac.toString().padStart(6, '0').replace(/0+$/, '');
643
+ return `${whole}.${fracStr}`;
644
+ }
645
+ /**
646
+ * Resolve the effective allowed-network list. Undefined / empty user config
647
+ * means "allow all EVM networks @x402/evm supports" — maximal interop default.
648
+ */
649
+ function resolveAllowedNetworks(allowed) {
650
+ if (allowed && allowed.length > 0)
651
+ return allowed;
652
+ return DEFAULT_EVM_NETWORKS;
653
+ }
654
+ /**
655
+ * Parse any reasonable amount representation (raw int string, decimal string,
656
+ * bigint, or number) into a USDC 6-decimal bigint.
657
+ *
658
+ * B2 fix: the previous version rejected decimal strings (regex `^\d+$`) and
659
+ * silently returned 0n, which caused zero-amount records when a facilitator
660
+ * reported settlement amounts like `"0.01"` instead of `"10000"`.
661
+ *
662
+ * Behavior:
663
+ * - "10000" → 10000n (raw 6-decimal)
664
+ * - "0.01" → 10000n (parsed as USD, normalized to 6 decimals)
665
+ * - "10" → 10000000n (treated as whole-USD if no decimal)
666
+ * - number 5 → 5000000n
667
+ * - 5n → 5n (bigint passed through)
668
+ *
669
+ * Ambiguity warning: a bare integer string like "10" could be either raw
670
+ * 6-decimal (0.00001 USDC) or whole USD (10 USDC). We use a heuristic: if
671
+ * the string contains a decimal point, parse as USD. Otherwise parse as raw.
672
+ * This matches the x402 spec which uses raw 6-decimal strings consistently.
673
+ */
674
+ function safeBigInt(v) {
675
+ try {
676
+ if (typeof v === 'bigint')
677
+ return v;
678
+ if (typeof v === 'number') {
679
+ if (!Number.isFinite(v) || v < 0)
680
+ return 0n;
681
+ // Whole number: treat as raw 6-decimal (spec-compliant shape)
682
+ if (Number.isInteger(v))
683
+ return BigInt(v);
684
+ // Decimal number: treat as USD, convert to 6-decimal
685
+ return parseUsdcAmount(v.toString());
686
+ }
687
+ if (typeof v === 'string') {
688
+ const trimmed = v.trim().replace(/^\$/, '');
689
+ if (/^\d+$/.test(trimmed))
690
+ return BigInt(trimmed);
691
+ if (/^\d+\.\d{1,6}$/.test(trimmed))
692
+ return parseUsdcAmount(trimmed);
693
+ }
694
+ }
695
+ catch {
696
+ // fall through
697
+ }
698
+ return 0n;
699
+ }
511
700
  //# sourceMappingURL=X402Adapter.js.map