@debridge-finance/borderless-validation-sdk 0.3.1 → 0.4.0

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 (284) hide show
  1. package/README.md +120 -11
  2. package/dist/cjs/graylists/evm/token-permit-metadata.js +7 -1
  3. package/dist/cjs/graylists/evm/token-permit-metadata.js.map +1 -1
  4. package/dist/cjs/helpers/address.js +139 -63
  5. package/dist/cjs/helpers/address.js.map +1 -1
  6. package/dist/cjs/helpers/amount.js +8 -0
  7. package/dist/cjs/helpers/amount.js.map +1 -0
  8. package/dist/cjs/helpers/evm-dln-hook-pack.js +16 -4
  9. package/dist/cjs/helpers/evm-dln-hook-pack.js.map +1 -1
  10. package/dist/cjs/helpers/evm-gas-compensation-tx.js +11 -26
  11. package/dist/cjs/helpers/evm-gas-compensation-tx.js.map +1 -1
  12. package/dist/cjs/helpers/evm-intent-id-abi.js +8 -1
  13. package/dist/cjs/helpers/evm-intent-id-abi.js.map +1 -1
  14. package/dist/cjs/helpers/evm-trade-amounts.js +11 -3
  15. package/dist/cjs/helpers/evm-trade-amounts.js.map +1 -1
  16. package/dist/cjs/helpers/expected-delegate.js +2 -6
  17. package/dist/cjs/helpers/expected-delegate.js.map +1 -1
  18. package/dist/cjs/helpers/gas-compensation.js +8 -4
  19. package/dist/cjs/helpers/gas-compensation.js.map +1 -1
  20. package/dist/cjs/helpers/interactions.js +4 -20
  21. package/dist/cjs/helpers/interactions.js.map +1 -1
  22. package/dist/cjs/helpers/synthetic-native-wrap.js +3 -2
  23. package/dist/cjs/helpers/synthetic-native-wrap.js.map +1 -1
  24. package/dist/cjs/types/enums.js +1 -0
  25. package/dist/cjs/types/enums.js.map +1 -1
  26. package/dist/cjs/types/request.js +15 -2
  27. package/dist/cjs/types/request.js.map +1 -1
  28. package/dist/cjs/types/response.js +2 -1
  29. package/dist/cjs/types/response.js.map +1 -1
  30. package/dist/cjs/types/validation.js +21 -3
  31. package/dist/cjs/types/validation.js.map +1 -1
  32. package/dist/cjs/validations/compensation-hook.js +4 -5
  33. package/dist/cjs/validations/compensation-hook.js.map +1 -1
  34. package/dist/cjs/validations/cost-token.js +9 -1
  35. package/dist/cjs/validations/cost-token.js.map +1 -1
  36. package/dist/cjs/validations/echo-fields.js +18 -0
  37. package/dist/cjs/validations/echo-fields.js.map +1 -1
  38. package/dist/cjs/validations/evm-caveats.js +3 -0
  39. package/dist/cjs/validations/evm-caveats.js.map +1 -1
  40. package/dist/cjs/validations/evm-on-chain-intent.js +150 -0
  41. package/dist/cjs/validations/evm-on-chain-intent.js.map +1 -0
  42. package/dist/cjs/validations/evm-op-budget.js +23 -3
  43. package/dist/cjs/validations/evm-op-budget.js.map +1 -1
  44. package/dist/cjs/validations/evm-op-hook.js +6 -6
  45. package/dist/cjs/validations/evm-op-hook.js.map +1 -1
  46. package/dist/cjs/validations/evm-permit.js +17 -6
  47. package/dist/cjs/validations/evm-permit.js.map +1 -1
  48. package/dist/cjs/validations/evm-permit2.js +14 -6
  49. package/dist/cjs/validations/evm-permit2.js.map +1 -1
  50. package/dist/cjs/validations/evm-required-actions.js +26 -7
  51. package/dist/cjs/validations/evm-required-actions.js.map +1 -1
  52. package/dist/cjs/validations/evm-sign712-metamask.js +3 -126
  53. package/dist/cjs/validations/evm-sign712-metamask.js.map +1 -1
  54. package/dist/cjs/validations/evm-sign712-shared.js +127 -0
  55. package/dist/cjs/validations/evm-sign712-shared.js.map +1 -1
  56. package/dist/cjs/validations/evm-sign712.js +18 -2
  57. package/dist/cjs/validations/evm-sign712.js.map +1 -1
  58. package/dist/cjs/validations/evm-sign7702.js +2 -0
  59. package/dist/cjs/validations/evm-sign7702.js.map +1 -1
  60. package/dist/cjs/validations/evm-transaction.js +7 -1
  61. package/dist/cjs/validations/evm-transaction.js.map +1 -1
  62. package/dist/cjs/validations/external-call.js +9 -20
  63. package/dist/cjs/validations/external-call.js.map +1 -1
  64. package/dist/cjs/validations/hook-version.js +35 -0
  65. package/dist/cjs/validations/hook-version.js.map +1 -0
  66. package/dist/cjs/validations/hooks-common.js +74 -199
  67. package/dist/cjs/validations/hooks-common.js.map +1 -1
  68. package/dist/cjs/validations/intent-destinations.js +52 -50
  69. package/dist/cjs/validations/intent-destinations.js.map +1 -1
  70. package/dist/cjs/validations/intent-tokens.js +319 -79
  71. package/dist/cjs/validations/intent-tokens.js.map +1 -1
  72. package/dist/cjs/validations/intent.js +34 -26
  73. package/dist/cjs/validations/intent.js.map +1 -1
  74. package/dist/cjs/validations/max-reserved.js +54 -0
  75. package/dist/cjs/validations/max-reserved.js.map +1 -0
  76. package/dist/cjs/validations/on-chain-intent-submission.js +70 -0
  77. package/dist/cjs/validations/on-chain-intent-submission.js.map +1 -0
  78. package/dist/cjs/validations/ptp.js +9 -6
  79. package/dist/cjs/validations/ptp.js.map +1 -1
  80. package/dist/cjs/validations/refill.js +11 -5
  81. package/dist/cjs/validations/refill.js.map +1 -1
  82. package/dist/cjs/validations/solana-hook.js +12 -64
  83. package/dist/cjs/validations/solana-hook.js.map +1 -1
  84. package/dist/cjs/validations/solana-op-budget.js +1 -1
  85. package/dist/cjs/validations/solana-op-budget.js.map +1 -1
  86. package/dist/cjs/validations/solana-op-wrap.js +29 -47
  87. package/dist/cjs/validations/solana-op-wrap.js.map +1 -1
  88. package/dist/cjs/validations/solana-required-actions.js +2 -2
  89. package/dist/cjs/validations/solana-required-actions.js.map +1 -1
  90. package/dist/cjs/validations/solana-transaction.js +4 -53
  91. package/dist/cjs/validations/solana-transaction.js.map +1 -1
  92. package/dist/cjs/validations/total-dst-amount.js +19 -3
  93. package/dist/cjs/validations/total-dst-amount.js.map +1 -1
  94. package/dist/cjs/validations/trade-grouping.js +35 -9
  95. package/dist/cjs/validations/trade-grouping.js.map +1 -1
  96. package/dist/cjs/validations/trade-matching.js +1 -1
  97. package/dist/cjs/validations/trade-matching.js.map +1 -1
  98. package/dist/cjs/validations/trades.js +29 -9
  99. package/dist/cjs/validations/trades.js.map +1 -1
  100. package/dist/cjs/validator.js +28 -17
  101. package/dist/cjs/validator.js.map +1 -1
  102. package/dist/cjs/whitelists/environment-config.js +9 -28
  103. package/dist/cjs/whitelists/environment-config.js.map +1 -1
  104. package/dist/cjs/whitelists/types.js +2 -5
  105. package/dist/cjs/whitelists/types.js.map +1 -1
  106. package/dist/esm/graylists/evm/token-permit-metadata.js +7 -1
  107. package/dist/esm/graylists/evm/token-permit-metadata.js.map +1 -1
  108. package/dist/esm/helpers/address.js +133 -61
  109. package/dist/esm/helpers/address.js.map +1 -1
  110. package/dist/esm/helpers/amount.js +5 -0
  111. package/dist/esm/helpers/amount.js.map +1 -0
  112. package/dist/esm/helpers/evm-dln-hook-pack.js +15 -4
  113. package/dist/esm/helpers/evm-dln-hook-pack.js.map +1 -1
  114. package/dist/esm/helpers/evm-gas-compensation-tx.js +12 -27
  115. package/dist/esm/helpers/evm-gas-compensation-tx.js.map +1 -1
  116. package/dist/esm/helpers/evm-intent-id-abi.js +7 -0
  117. package/dist/esm/helpers/evm-intent-id-abi.js.map +1 -1
  118. package/dist/esm/helpers/evm-trade-amounts.js +12 -4
  119. package/dist/esm/helpers/evm-trade-amounts.js.map +1 -1
  120. package/dist/esm/helpers/expected-delegate.js +2 -6
  121. package/dist/esm/helpers/expected-delegate.js.map +1 -1
  122. package/dist/esm/helpers/gas-compensation.js +9 -5
  123. package/dist/esm/helpers/gas-compensation.js.map +1 -1
  124. package/dist/esm/helpers/interactions.js +5 -21
  125. package/dist/esm/helpers/interactions.js.map +1 -1
  126. package/dist/esm/helpers/synthetic-native-wrap.js +4 -3
  127. package/dist/esm/helpers/synthetic-native-wrap.js.map +1 -1
  128. package/dist/esm/types/enums.js +1 -0
  129. package/dist/esm/types/enums.js.map +1 -1
  130. package/dist/esm/types/request.js +15 -2
  131. package/dist/esm/types/request.js.map +1 -1
  132. package/dist/esm/types/response.js +3 -2
  133. package/dist/esm/types/response.js.map +1 -1
  134. package/dist/esm/types/validation.js +21 -3
  135. package/dist/esm/types/validation.js.map +1 -1
  136. package/dist/esm/validations/compensation-hook.js +4 -5
  137. package/dist/esm/validations/compensation-hook.js.map +1 -1
  138. package/dist/esm/validations/cost-token.js +9 -1
  139. package/dist/esm/validations/cost-token.js.map +1 -1
  140. package/dist/esm/validations/echo-fields.js +18 -0
  141. package/dist/esm/validations/echo-fields.js.map +1 -1
  142. package/dist/esm/validations/evm-caveats.js +3 -0
  143. package/dist/esm/validations/evm-caveats.js.map +1 -1
  144. package/dist/esm/validations/evm-on-chain-intent.js +147 -0
  145. package/dist/esm/validations/evm-on-chain-intent.js.map +1 -0
  146. package/dist/esm/validations/evm-op-budget.js +25 -5
  147. package/dist/esm/validations/evm-op-budget.js.map +1 -1
  148. package/dist/esm/validations/evm-op-hook.js +8 -8
  149. package/dist/esm/validations/evm-op-hook.js.map +1 -1
  150. package/dist/esm/validations/evm-permit.js +18 -7
  151. package/dist/esm/validations/evm-permit.js.map +1 -1
  152. package/dist/esm/validations/evm-permit2.js +15 -7
  153. package/dist/esm/validations/evm-permit2.js.map +1 -1
  154. package/dist/esm/validations/evm-required-actions.js +28 -9
  155. package/dist/esm/validations/evm-required-actions.js.map +1 -1
  156. package/dist/esm/validations/evm-sign712-metamask.js +4 -126
  157. package/dist/esm/validations/evm-sign712-metamask.js.map +1 -1
  158. package/dist/esm/validations/evm-sign712-shared.js +126 -1
  159. package/dist/esm/validations/evm-sign712-shared.js.map +1 -1
  160. package/dist/esm/validations/evm-sign712.js +17 -2
  161. package/dist/esm/validations/evm-sign712.js.map +1 -1
  162. package/dist/esm/validations/evm-sign7702.js +2 -0
  163. package/dist/esm/validations/evm-sign7702.js.map +1 -1
  164. package/dist/esm/validations/evm-transaction.js +7 -1
  165. package/dist/esm/validations/evm-transaction.js.map +1 -1
  166. package/dist/esm/validations/external-call.js +10 -21
  167. package/dist/esm/validations/external-call.js.map +1 -1
  168. package/dist/esm/validations/hook-version.js +31 -0
  169. package/dist/esm/validations/hook-version.js.map +1 -0
  170. package/dist/esm/validations/hooks-common.js +75 -200
  171. package/dist/esm/validations/hooks-common.js.map +1 -1
  172. package/dist/esm/validations/intent-destinations.js +53 -51
  173. package/dist/esm/validations/intent-destinations.js.map +1 -1
  174. package/dist/esm/validations/intent-tokens.js +320 -80
  175. package/dist/esm/validations/intent-tokens.js.map +1 -1
  176. package/dist/esm/validations/intent.js +34 -26
  177. package/dist/esm/validations/intent.js.map +1 -1
  178. package/dist/esm/validations/max-reserved.js +51 -0
  179. package/dist/esm/validations/max-reserved.js.map +1 -0
  180. package/dist/esm/validations/on-chain-intent-submission.js +67 -0
  181. package/dist/esm/validations/on-chain-intent-submission.js.map +1 -0
  182. package/dist/esm/validations/ptp.js +9 -6
  183. package/dist/esm/validations/ptp.js.map +1 -1
  184. package/dist/esm/validations/refill.js +11 -5
  185. package/dist/esm/validations/refill.js.map +1 -1
  186. package/dist/esm/validations/solana-hook.js +12 -64
  187. package/dist/esm/validations/solana-hook.js.map +1 -1
  188. package/dist/esm/validations/solana-op-budget.js +1 -1
  189. package/dist/esm/validations/solana-op-budget.js.map +1 -1
  190. package/dist/esm/validations/solana-op-wrap.js +30 -48
  191. package/dist/esm/validations/solana-op-wrap.js.map +1 -1
  192. package/dist/esm/validations/solana-required-actions.js +2 -2
  193. package/dist/esm/validations/solana-required-actions.js.map +1 -1
  194. package/dist/esm/validations/solana-transaction.js +4 -53
  195. package/dist/esm/validations/solana-transaction.js.map +1 -1
  196. package/dist/esm/validations/total-dst-amount.js +20 -4
  197. package/dist/esm/validations/total-dst-amount.js.map +1 -1
  198. package/dist/esm/validations/trade-grouping.js +35 -10
  199. package/dist/esm/validations/trade-grouping.js.map +1 -1
  200. package/dist/esm/validations/trade-matching.js +1 -1
  201. package/dist/esm/validations/trade-matching.js.map +1 -1
  202. package/dist/esm/validations/trades.js +29 -9
  203. package/dist/esm/validations/trades.js.map +1 -1
  204. package/dist/esm/validator.js +28 -17
  205. package/dist/esm/validator.js.map +1 -1
  206. package/dist/esm/whitelists/environment-config.js +8 -27
  207. package/dist/esm/whitelists/environment-config.js.map +1 -1
  208. package/dist/esm/whitelists/types.js +3 -6
  209. package/dist/esm/whitelists/types.js.map +1 -1
  210. package/dist/types/graylists/evm/token-permit-metadata.d.ts.map +1 -1
  211. package/dist/types/helpers/address.d.ts +9 -5
  212. package/dist/types/helpers/address.d.ts.map +1 -1
  213. package/dist/types/helpers/amount.d.ts +2 -0
  214. package/dist/types/helpers/amount.d.ts.map +1 -0
  215. package/dist/types/helpers/evm-dln-hook-pack.d.ts +1 -0
  216. package/dist/types/helpers/evm-dln-hook-pack.d.ts.map +1 -1
  217. package/dist/types/helpers/evm-gas-compensation-tx.d.ts +1 -3
  218. package/dist/types/helpers/evm-gas-compensation-tx.d.ts.map +1 -1
  219. package/dist/types/helpers/evm-intent-id-abi.d.ts +111 -0
  220. package/dist/types/helpers/evm-intent-id-abi.d.ts.map +1 -1
  221. package/dist/types/helpers/evm-trade-amounts.d.ts.map +1 -1
  222. package/dist/types/helpers/expected-delegate.d.ts.map +1 -1
  223. package/dist/types/helpers/gas-compensation.d.ts.map +1 -1
  224. package/dist/types/helpers/interactions.d.ts.map +1 -1
  225. package/dist/types/helpers/synthetic-native-wrap.d.ts.map +1 -1
  226. package/dist/types/types/enums.d.ts +1 -0
  227. package/dist/types/types/enums.d.ts.map +1 -1
  228. package/dist/types/types/request.d.ts +32 -23
  229. package/dist/types/types/request.d.ts.map +1 -1
  230. package/dist/types/types/response.d.ts +10 -42
  231. package/dist/types/types/response.d.ts.map +1 -1
  232. package/dist/types/types/validation.d.ts +19 -4
  233. package/dist/types/types/validation.d.ts.map +1 -1
  234. package/dist/types/validations/compensation-hook.d.ts +1 -2
  235. package/dist/types/validations/compensation-hook.d.ts.map +1 -1
  236. package/dist/types/validations/cost-token.d.ts.map +1 -1
  237. package/dist/types/validations/echo-fields.d.ts.map +1 -1
  238. package/dist/types/validations/evm-caveats.d.ts.map +1 -1
  239. package/dist/types/validations/evm-on-chain-intent.d.ts +4 -0
  240. package/dist/types/validations/evm-on-chain-intent.d.ts.map +1 -0
  241. package/dist/types/validations/evm-op-budget.d.ts.map +1 -1
  242. package/dist/types/validations/evm-op-hook.d.ts.map +1 -1
  243. package/dist/types/validations/evm-permit.d.ts.map +1 -1
  244. package/dist/types/validations/evm-permit2.d.ts.map +1 -1
  245. package/dist/types/validations/evm-required-actions.d.ts.map +1 -1
  246. package/dist/types/validations/evm-sign712-metamask.d.ts +0 -11
  247. package/dist/types/validations/evm-sign712-metamask.d.ts.map +1 -1
  248. package/dist/types/validations/evm-sign712-shared.d.ts +12 -0
  249. package/dist/types/validations/evm-sign712-shared.d.ts.map +1 -1
  250. package/dist/types/validations/evm-sign712.d.ts +1 -0
  251. package/dist/types/validations/evm-sign712.d.ts.map +1 -1
  252. package/dist/types/validations/evm-sign7702.d.ts.map +1 -1
  253. package/dist/types/validations/evm-transaction.d.ts +1 -1
  254. package/dist/types/validations/evm-transaction.d.ts.map +1 -1
  255. package/dist/types/validations/external-call.d.ts.map +1 -1
  256. package/dist/types/validations/hook-version.d.ts +7 -0
  257. package/dist/types/validations/hook-version.d.ts.map +1 -0
  258. package/dist/types/validations/hooks-common.d.ts +1 -4
  259. package/dist/types/validations/hooks-common.d.ts.map +1 -1
  260. package/dist/types/validations/intent-destinations.d.ts.map +1 -1
  261. package/dist/types/validations/intent-tokens.d.ts.map +1 -1
  262. package/dist/types/validations/max-reserved.d.ts +3 -0
  263. package/dist/types/validations/max-reserved.d.ts.map +1 -0
  264. package/dist/types/validations/on-chain-intent-submission.d.ts +3 -0
  265. package/dist/types/validations/on-chain-intent-submission.d.ts.map +1 -0
  266. package/dist/types/validations/ptp.d.ts.map +1 -1
  267. package/dist/types/validations/refill.d.ts.map +1 -1
  268. package/dist/types/validations/solana-hook.d.ts +1 -2
  269. package/dist/types/validations/solana-hook.d.ts.map +1 -1
  270. package/dist/types/validations/solana-op-wrap.d.ts +2 -2
  271. package/dist/types/validations/solana-op-wrap.d.ts.map +1 -1
  272. package/dist/types/validations/solana-required-actions.d.ts.map +1 -1
  273. package/dist/types/validations/solana-transaction.d.ts +3 -3
  274. package/dist/types/validations/solana-transaction.d.ts.map +1 -1
  275. package/dist/types/validations/total-dst-amount.d.ts.map +1 -1
  276. package/dist/types/validations/trade-grouping.d.ts +1 -0
  277. package/dist/types/validations/trade-grouping.d.ts.map +1 -1
  278. package/dist/types/validations/trades.d.ts.map +1 -1
  279. package/dist/types/validator.d.ts.map +1 -1
  280. package/dist/types/whitelists/environment-config.d.ts +0 -1
  281. package/dist/types/whitelists/environment-config.d.ts.map +1 -1
  282. package/dist/types/whitelists/types.d.ts +0 -1
  283. package/dist/types/whitelists/types.d.ts.map +1 -1
  284. package/package.json +5 -2
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateIntentTokens = validateIntentTokens;
4
4
  const types_1 = require("../types");
5
+ const trade_grouping_1 = require("./trade-grouping");
5
6
  const address_1 = require("../helpers/address");
6
7
  const evm_token_1 = require("../helpers/evm-token");
7
8
  const subscription_to_chain_id_1 = require("../whitelists/subscription-to-chain-id");
@@ -14,6 +15,7 @@ function validateIntentTokens(ctx, basePath) {
14
15
  ...resolution.violations,
15
16
  ...validateInputTokens(intent.inputToken, ctx, resolution.expected, `${basePath}.inputToken`),
16
17
  ...validateGiveTokens(ctx, `${basePath}.giveToken`),
18
+ ...validateTakeTokenZeroMode(ctx, `${basePath}.takeToken`),
17
19
  ...validateTakeTokens(ctx, `${basePath}.takeToken`),
18
20
  ];
19
21
  }
@@ -24,7 +26,7 @@ function validateIntentTokens(ctx, basePath) {
24
26
  // so the caller knows which trade is unverifiable.
25
27
  function resolveIntentInputTokens(ctx) {
26
28
  const group = ctx.tradeGroup;
27
- const expected = new Set();
29
+ const expected = new Map();
28
30
  const violations = [];
29
31
  for (let i = 0; i < ctx.intentTrades.length; i++) {
30
32
  const trade = ctx.intentTrades[i];
@@ -34,12 +36,27 @@ function resolveIntentInputTokens(ctx) {
34
36
  code: types_1.ViolationCode.IntentInputTokenWrappedAddressUnknown,
35
37
  message: "cannot resolve wrapped token address: native token on a"
36
38
  + " chain with no WETH mapping in the whitelist",
37
- path: `request.trades[${group.tradeIndices[ctx.intentTradeIndices[i]]}].srcChainTokenIn`,
39
+ path: `request.trades[${(0, trade_grouping_1.getRequestTradeIndex)(ctx, i)}].srcChainTokenIn`,
38
40
  actual: trade.srcChainTokenIn,
39
41
  });
40
42
  continue;
41
43
  }
42
- expected.add((0, address_1.normalizeAddress)(group.realChainId, resolved));
44
+ const tokenKey = (0, address_1.getAddressIdentityKey)(group.realChainId, resolved);
45
+ if (tokenKey === undefined) {
46
+ violations.push({
47
+ code: types_1.ViolationCode.RequestTradeAddressInvalid,
48
+ message: "request.trades[].srcChainTokenIn is not a valid address"
49
+ + " for srcChainId",
50
+ path: `request.trades[${(0, trade_grouping_1.getRequestTradeIndex)(ctx, i)}].srcChainTokenIn`,
51
+ expected: {
52
+ srcChainId: trade.srcChainId,
53
+ realChainId: group.realChainId,
54
+ },
55
+ actual: trade.srcChainTokenIn,
56
+ });
57
+ continue;
58
+ }
59
+ expected.set(tokenKey, resolved);
43
60
  }
44
61
  return { expected, violations };
45
62
  }
@@ -51,11 +68,21 @@ function validateInputTokens(inputTokens, ctx, expectedTokens, basePath) {
51
68
  const group = ctx.tradeGroup;
52
69
  const actual = inputTokens ?? [];
53
70
  const violations = [];
54
- const actualAddresses = actual.map(t => (0, address_1.normalizeAddress)(group.realChainId, t.address));
55
71
  const firstOccurrence = new Map();
56
- for (let i = 0; i < actualAddresses.length; i++) {
57
- const address = actualAddresses[i];
58
- const earlier = firstOccurrence.get(address);
72
+ for (let i = 0; i < actual.length; i++) {
73
+ const addressKey = (0, address_1.getAddressIdentityKey)(group.realChainId, actual[i].address);
74
+ if (addressKey === undefined) {
75
+ violations.push({
76
+ code: types_1.ViolationCode.IntentInputTokenAddressMismatch,
77
+ message: "intent.inputToken[].address is not a valid address for"
78
+ + " the intent source chain",
79
+ path: `${basePath}[${i}].address`,
80
+ expected: Array.from(expectedTokens.values()),
81
+ actual: actual[i].address,
82
+ });
83
+ continue;
84
+ }
85
+ const earlier = firstOccurrence.get(addressKey);
59
86
  if (earlier !== undefined) {
60
87
  violations.push({
61
88
  code: types_1.ViolationCode.IntentInputTokenDuplicate,
@@ -63,38 +90,38 @@ function validateInputTokens(inputTokens, ctx, expectedTokens, basePath) {
63
90
  + `${earlier} — each srcChainTokenIn (or its wrapped `
64
91
  + `equivalent) must appear once`,
65
92
  path: `${basePath}[${i}].address`,
66
- actual: address,
93
+ actual: actual[i].address,
67
94
  });
68
95
  }
69
96
  else {
70
- firstOccurrence.set(address, i);
97
+ firstOccurrence.set(addressKey, i);
71
98
  }
72
99
  }
73
- for (const expectedToken of expectedTokens) {
74
- if (!firstOccurrence.has(expectedToken)) {
100
+ for (const [expectedKey, expectedAddress] of expectedTokens) {
101
+ if (!firstOccurrence.has(expectedKey)) {
75
102
  violations.push({
76
103
  code: types_1.ViolationCode.IntentInputTokenMissing,
77
104
  message: "an expected srcChainTokenIn address (or its wrapped "
78
105
  + "equivalent) is missing from intent.inputToken[]",
79
106
  path: basePath,
80
- expected: expectedToken,
107
+ expected: expectedAddress,
81
108
  });
82
109
  }
83
110
  }
84
111
  // Validate each unique entry (duplicates were already reported above)
85
- for (const [address, i] of firstOccurrence) {
86
- if (!expectedTokens.has(address)) {
112
+ for (const [addressKey, i] of firstOccurrence) {
113
+ if (!expectedTokens.has(addressKey)) {
87
114
  violations.push({
88
115
  code: types_1.ViolationCode.IntentInputTokenAddressMismatch,
89
116
  message: "intent.inputToken[].address does not match any request"
90
117
  + " srcChainTokenIn (including native-to-wrapped resolution)",
91
118
  path: `${basePath}[${i}].address`,
92
- expected: Array.from(expectedTokens),
93
- actual: address,
119
+ expected: Array.from(expectedTokens.values()),
120
+ actual: actual[i].address,
94
121
  });
95
122
  continue;
96
123
  }
97
- violations.push(...validateConstraintBudget(actual[i], ctx, address, `${basePath}[${i}]`));
124
+ violations.push(...validateConstraintBudget(actual[i], ctx, addressKey, `${basePath}[${i}]`));
98
125
  }
99
126
  return violations;
100
127
  }
@@ -108,7 +135,7 @@ function validateInputTokens(inputTokens, ctx, expectedTokens, basePath) {
108
135
  //
109
136
  // Sanity bounds on `response.srcChainTokenIn.amount` itself live in
110
137
  // `validateTrades`.
111
- function validateConstraintBudget(inputToken, ctx, normalizedAddress, basePath) {
138
+ function validateConstraintBudget(inputToken, ctx, addressKey, basePath) {
112
139
  // MEDIUM: exact per-intent trade subset is unverifiable for partial quote
113
140
  // groups because the public response strips the hidden grouping keys.
114
141
  if (ctx.quoteTradeSubsetUnverifiable)
@@ -119,10 +146,11 @@ function validateConstraintBudget(inputToken, ctx, normalizedAddress, basePath)
119
146
  .filter(({ trade }) => {
120
147
  const resolved = (0, evm_token_1.resolveWrappedTokenAddress)(trade.srcChainTokenIn, realChainId);
121
148
  // Safe skip: unresolved input tokens are already reported by
122
- // `resolveGroupInputTokens`.
149
+ // `resolveIntentInputTokens`.
123
150
  if (resolved === undefined)
124
151
  return false;
125
- return (0, address_1.normalizeAddress)(realChainId, resolved) === normalizedAddress;
152
+ const tradeTokenKey = (0, address_1.getAddressIdentityKey)(realChainId, resolved);
153
+ return tradeTokenKey !== undefined && tradeTokenKey === addressKey;
126
154
  });
127
155
  // Safe skip: missing response trade at a contributing index is reported
128
156
  // by `validateTrades` as `TradeCountMismatch`; computing a partial sum here
@@ -192,32 +220,29 @@ function validateGiveTokens(ctx, basePath) {
192
220
  return [];
193
221
  }
194
222
  const violations = [];
195
- const expectedFormat = (0, address_1.getChainEngine)(group.realChainId);
223
+ const expectedEngine = (0, address_1.getChainEngine)(group.realChainId);
196
224
  for (let i = 0; i < giveTokens.length; i++) {
197
- const normalized = (0, address_1.normalizeAddress)(group.realChainId, giveTokens[i].inputTokenAddress);
198
- if (!allowed.has(normalized)) {
225
+ const addressKey = (0, address_1.getAddressIdentityKey)(group.realChainId, giveTokens[i].inputTokenAddress);
226
+ if (addressKey === undefined || !allowed.has(addressKey)) {
199
227
  violations.push({
200
228
  code: types_1.ViolationCode.IntentGiveTokenInputAddressMismatch,
201
229
  message: "intent.giveToken[].inputTokenAddress must equal one of"
202
230
  + " intent.inputToken[].address used by a cross-chain"
203
231
  + " trade in the group",
204
232
  path: `${basePath}[${i}].inputTokenAddress`,
205
- expected: Array.from(allowed),
233
+ expected: Array.from(allowed.values()),
206
234
  actual: giveTokens[i].inputTokenAddress,
207
235
  });
208
236
  }
209
237
  // CRITICAL: giveTokenAddress is a bridge token in src chain — its value
210
238
  // is API-determined and unpredictable, but its format must match the
211
239
  // intent chain's engine.
212
- const actualFormat = (0, address_1.detectChainEngine)(giveTokens[i].giveTokenAddress);
213
- if (actualFormat !== expectedFormat) {
240
+ if (!(0, address_1.isAddressRepresentationValidForChain)(group.realChainId, giveTokens[i].giveTokenAddress)) {
214
241
  violations.push({
215
242
  code: types_1.ViolationCode.IntentGiveTokenAddressFormatMismatch,
216
- message: `intent.giveToken[].giveTokenAddress format (${types_1.ChainEngine[actualFormat]})`
217
- + ` does not match the intent chain engine`
218
- + ` (${types_1.ChainEngine[expectedFormat]})`,
243
+ message: "intent.giveToken[].giveTokenAddress does not use a supported"
244
+ + ` format for the intent chain engine (${types_1.ChainEngine[expectedEngine]})`,
219
245
  path: `${basePath}[${i}].giveTokenAddress`,
220
- expected: types_1.ChainEngine[expectedFormat],
221
246
  actual: giveTokens[i].giveTokenAddress,
222
247
  });
223
248
  }
@@ -229,43 +254,168 @@ function validateGiveTokens(ctx, basePath) {
229
254
  function collectInputAddressesForCrossChainTrades(ctx) {
230
255
  const intent = ctx.intentPayload.intent;
231
256
  const group = ctx.tradeGroup;
232
- const inputAddresses = new Set((intent.inputToken ?? []).map(t => (0, address_1.normalizeAddress)(group.realChainId, t.address)));
233
- const result = new Set();
257
+ const inputAddresses = new Map();
258
+ for (const inputToken of intent.inputToken ?? []) {
259
+ const inputTokenKey = (0, address_1.getAddressIdentityKey)(group.realChainId, inputToken.address);
260
+ if (inputTokenKey !== undefined) {
261
+ inputAddresses.set(inputTokenKey, inputToken.address);
262
+ }
263
+ }
264
+ const result = new Map();
234
265
  for (const trade of ctx.intentTrades) {
235
266
  if (!isCrossChain(trade))
236
267
  continue;
237
268
  const resolved = (0, evm_token_1.resolveWrappedTokenAddress)(trade.srcChainTokenIn, group.realChainId);
238
269
  // Safe skip: unresolved input tokens are already reported by
239
- // resolveGroupInputTokens.
270
+ // `resolveIntentInputTokens`.
240
271
  if (resolved === undefined)
241
272
  continue;
242
- const normalized = (0, address_1.normalizeAddress)(group.realChainId, resolved);
243
- if (inputAddresses.has(normalized)) {
244
- result.add(normalized);
245
- }
273
+ const addressKey = (0, address_1.getAddressIdentityKey)(group.realChainId, resolved);
274
+ if (addressKey === undefined)
275
+ continue;
276
+ const inputAddress = inputAddresses.get(addressKey);
277
+ if (inputAddress !== undefined)
278
+ result.set(addressKey, inputAddress);
246
279
  }
247
280
  return result;
248
281
  }
249
282
  function isCrossChain(trade) {
250
283
  return trade.srcChainId !== trade.dstChainId;
251
284
  }
252
- // CRITICAL: intent.takeToken[] must cover the unique destination
253
- // (dstChainId, dstChainTokenOut) pairs from this concrete intent's trades. The
254
- // API keys actual take-token entries by source/from token plus destination token,
255
- // so duplicate detection uses the full
256
- // (fromTokenAddress, takeTokenChainId, takeTokenAddress) identity.
285
+ // CRITICAL: trades folded into one intent must not mix zero and non-zero
286
+ // destination amounts for the same source/destination token pair. The intent
287
+ // stores one exchange ratio per pair, so mixing modes would collapse the
288
+ // signed ratio to zero for every trade in that pair.
289
+ function validateTakeTokenZeroMode(ctx, basePath) {
290
+ const modes = new Map();
291
+ const reportedPairs = new Set();
292
+ const violations = [];
293
+ for (let i = 0; i < ctx.intentResponseTrades.length; i++) {
294
+ const requestTrade = ctx.intentTrades[i];
295
+ const responseTrade = ctx.intentResponseTrades[i];
296
+ if (requestTrade === undefined || responseTrade === undefined) {
297
+ // Safe skip: missing trade pairs are reported by trade count and matching checks.
298
+ continue;
299
+ }
300
+ const entry = getTakeTokenZeroModeEntry(requestTrade, responseTrade);
301
+ if (entry === undefined)
302
+ continue;
303
+ const existing = modes.get(entry.key);
304
+ if (existing === undefined) {
305
+ modes.set(entry.key, {
306
+ fromChainId: entry.fromChainId,
307
+ fromTokenAddress: entry.fromTokenAddress,
308
+ takeChainId: entry.takeChainId,
309
+ takeTokenAddress: entry.takeTokenAddress,
310
+ isDestinationAmountZero: entry.isDestinationAmountZero,
311
+ amounts: [entry.amount],
312
+ });
313
+ continue;
314
+ }
315
+ existing.amounts.push(entry.amount);
316
+ if (existing.isDestinationAmountZero === entry.isDestinationAmountZero)
317
+ continue;
318
+ if (reportedPairs.has(entry.key))
319
+ continue;
320
+ reportedPairs.add(entry.key);
321
+ violations.push(createTakeTokenZeroModeViolation(existing, basePath));
322
+ }
323
+ return violations;
324
+ }
325
+ function getTakeTokenZeroModeEntry(requestTrade, responseTrade) {
326
+ let amount;
327
+ try {
328
+ amount = (0, integer_1.parseIntegerString)(responseTrade.dstChainTokenOut.amount);
329
+ }
330
+ catch {
331
+ // Safe skip: malformed destination amounts are reported by the trade-level checks.
332
+ return undefined;
333
+ }
334
+ if (amount < 0n) {
335
+ // Safe skip: legacy Solana negative outputs are validated separately.
336
+ return undefined;
337
+ }
338
+ const pair = resolveTakeTokenPairIdentity(requestTrade, responseTrade);
339
+ if (pair === undefined) {
340
+ // Safe skip: unknown chains, malformed token addresses, and native
341
+ // mapping gaps are reported by the dedicated trade/intent validators.
342
+ return undefined;
343
+ }
344
+ return {
345
+ ...pair,
346
+ amount: responseTrade.dstChainTokenOut.amount,
347
+ isDestinationAmountZero: amount === 0n,
348
+ };
349
+ }
350
+ function createTakeTokenZeroModeViolation(mode, basePath) {
351
+ return {
352
+ code: types_1.ViolationCode.IntentTakeTokenZeroModeMismatch,
353
+ message: "trades represented by one intent must not mix zero and non-zero"
354
+ + " destination amounts for the same token pair",
355
+ path: basePath,
356
+ actual: {
357
+ fromChainId: mode.fromChainId,
358
+ fromTokenAddress: mode.fromTokenAddress,
359
+ takeChainId: mode.takeChainId,
360
+ takeTokenAddress: mode.takeTokenAddress,
361
+ destinationAmounts: [...mode.amounts],
362
+ },
363
+ };
364
+ }
365
+ function resolveTakeTokenPairIdentity(requestTrade, responseTrade) {
366
+ const fromRealChainId = (0, subscription_to_chain_id_1.resolveChainId)(requestTrade.srcChainId);
367
+ const takeRealChainId = (0, subscription_to_chain_id_1.resolveChainId)(requestTrade.dstChainId);
368
+ if (fromRealChainId === undefined || takeRealChainId === undefined)
369
+ return undefined;
370
+ const fromToken = responseTrade.srcChainTokenOut ?? responseTrade.srcChainTokenIn;
371
+ const canonicalFromTokenAddress = fromRealChainId === takeRealChainId
372
+ ? (0, evm_token_1.resolveWrappedTokenAddress)(fromToken.address, fromRealChainId)
373
+ : fromToken.address;
374
+ if (canonicalFromTokenAddress === undefined)
375
+ return undefined;
376
+ const takeTokenAddress = responseTrade.dstChainTokenOut.address;
377
+ const fromTokenKey = (0, address_1.getAddressIdentityKey)(fromRealChainId, canonicalFromTokenAddress);
378
+ const takeTokenKey = (0, address_1.getAddressIdentityKey)(takeRealChainId, takeTokenAddress);
379
+ if (fromTokenKey === undefined || takeTokenKey === undefined)
380
+ return undefined;
381
+ return {
382
+ key: [
383
+ fromRealChainId,
384
+ fromTokenKey,
385
+ takeRealChainId,
386
+ takeTokenKey,
387
+ ].join(":"),
388
+ fromChainId: fromRealChainId,
389
+ fromTokenAddress: canonicalFromTokenAddress,
390
+ takeChainId: takeRealChainId,
391
+ takeTokenAddress,
392
+ };
393
+ }
394
+ // CRITICAL: intent.takeToken[] must cover every unique
395
+ // (fromTokenAddress, takeTokenChainId, takeTokenAddress) identity represented
396
+ // by this concrete intent's trades. Missing and duplicate checks use the same
397
+ // full identity so one source token cannot borrow another source token's output
398
+ // constraint merely because both trades share a destination token.
257
399
  // CRITICAL: fromTokenAddress must be in inputToken[] (same-chain) or giveToken[]
258
400
  // (cross-chain).
259
401
  function validateTakeTokens(ctx, basePath) {
260
402
  const intent = ctx.intentPayload.intent;
261
403
  const group = ctx.tradeGroup;
262
404
  const violations = [];
263
- const expectedDestinations = buildExpectedTakeTokenDestinations(ctx);
405
+ const expectedResult = buildExpectedTakeTokens(ctx);
406
+ const expectedIdentities = expectedResult.identities;
407
+ const expectedDestinations = expectedResult.destinations;
408
+ violations.push(...expectedResult.violations);
264
409
  const actualKeys = intent.takeToken.map(takeToken => buildTakeTokenIdentityKey(takeToken, group.realChainId));
265
- const actualDestinations = new Set(intent.takeToken.map(takeToken => buildTakeTokenDestinationKey(takeToken)));
410
+ const actualIdentityKeys = new Set(actualKeys.flatMap(key => key === undefined ? [] : [key]));
266
411
  const firstOccurrence = new Map();
412
+ const uniqueIndices = [];
267
413
  for (let i = 0; i < actualKeys.length; i++) {
268
414
  const key = actualKeys[i];
415
+ if (key === undefined) {
416
+ uniqueIndices.push(i);
417
+ continue;
418
+ }
269
419
  const earlier = firstOccurrence.get(key);
270
420
  if (earlier !== undefined) {
271
421
  violations.push({
@@ -274,57 +424,110 @@ function validateTakeTokens(ctx, basePath) {
274
424
  + ` index ${earlier} — each (fromTokenAddress,`
275
425
  + ` takeTokenChainId, takeTokenAddress) tuple must appear once`,
276
426
  path: `${basePath}[${i}]`,
277
- actual: key,
427
+ actual: {
428
+ fromTokenAddress: intent.takeToken[i].fromTokenAddress,
429
+ takeTokenChainId: intent.takeToken[i].takeTokenChainId,
430
+ takeTokenAddress: intent.takeToken[i].takeTokenAddress,
431
+ },
278
432
  });
279
433
  }
280
434
  else {
281
435
  firstOccurrence.set(key, i);
436
+ uniqueIndices.push(i);
282
437
  }
283
438
  }
284
- for (const expectedTakeToken of expectedDestinations) {
285
- if (!actualDestinations.has(expectedTakeToken)) {
439
+ for (const [expectedKey, expectedTakeToken] of expectedIdentities) {
440
+ if (!actualIdentityKeys.has(expectedKey)) {
286
441
  violations.push({
287
442
  code: types_1.ViolationCode.IntentTakeTokenMissing,
288
- message: "an expected (dstChainId, dstChainTokenOut) pair is"
289
- + " missing from intent.takeToken[]",
443
+ message: "an expected (fromTokenAddress, takeTokenChainId,"
444
+ + " takeTokenAddress) identity is missing from"
445
+ + " intent.takeToken[]",
290
446
  path: basePath,
291
447
  expected: expectedTakeToken,
292
448
  });
293
449
  }
294
450
  }
295
451
  // Validate each unique entry (duplicates are already reported above)
296
- for (const [, i] of firstOccurrence) {
452
+ for (const i of uniqueIndices) {
297
453
  violations.push(...validateSingleTakeToken(intent, intent.takeToken[i], group, expectedDestinations, `${basePath}[${i}]`));
298
454
  }
299
455
  return violations;
300
456
  }
301
- // Single source of truth for the "chainId:normalizedAddress" key format used
302
- // to compare takeToken entries as a set. If realChainId is unknown we skip
303
- // normalization so the function can still produce a deterministic key for
304
- // duplicate detection; the chainId mismatch is flagged separately.
457
+ // Single source of truth for the "chainId:canonicalAddress" key format used
458
+ // to compare takeToken entries as a set. Unknown chains and malformed
459
+ // addresses have no comparable identity.
305
460
  function buildChainTokenKey(chainId, realChainId, address) {
306
- const normalized = realChainId !== undefined
307
- ? (0, address_1.normalizeAddress)(realChainId, address)
308
- : address;
309
- return `${chainId}:${normalized}`;
461
+ if (realChainId === undefined)
462
+ return undefined;
463
+ const addressKey = (0, address_1.getAddressIdentityKey)(realChainId, address);
464
+ if (addressKey === undefined)
465
+ return undefined;
466
+ return `${chainId}:${addressKey}`;
310
467
  }
311
468
  function buildTakeTokenDestinationKey(takeToken) {
312
469
  return buildChainTokenKey(takeToken.takeTokenChainId, (0, subscription_to_chain_id_1.resolveChainId)(takeToken.takeTokenChainId), takeToken.takeTokenAddress);
313
470
  }
314
471
  function buildTakeTokenIdentityKey(takeToken, sourceRealChainId) {
315
- const fromKey = (0, address_1.normalizeAddress)(sourceRealChainId, takeToken.fromTokenAddress);
316
- const destinationKey = buildTakeTokenDestinationKey(takeToken);
317
- return `${fromKey}->${destinationKey}`;
472
+ const destinationRealChainId = (0, subscription_to_chain_id_1.resolveChainId)(takeToken.takeTokenChainId);
473
+ if (destinationRealChainId === undefined)
474
+ return undefined;
475
+ return buildResolvedTakeTokenIdentityKey(sourceRealChainId, takeToken.fromTokenAddress, destinationRealChainId, takeToken.takeTokenAddress);
318
476
  }
319
- function buildExpectedTakeTokenDestinations(ctx) {
320
- const set = new Set();
321
- for (const trade of ctx.intentTrades) {
477
+ function buildResolvedTakeTokenIdentityKey(sourceRealChainId, fromTokenAddress, destinationRealChainId, takeTokenAddress) {
478
+ const fromKey = (0, address_1.getAddressIdentityKey)(sourceRealChainId, fromTokenAddress);
479
+ const takeKey = (0, address_1.getAddressIdentityKey)(destinationRealChainId, takeTokenAddress);
480
+ if (fromKey === undefined || takeKey === undefined)
481
+ return undefined;
482
+ return [
483
+ sourceRealChainId,
484
+ fromKey,
485
+ destinationRealChainId,
486
+ takeKey,
487
+ ].join(":");
488
+ }
489
+ function buildExpectedTakeTokens(ctx) {
490
+ const identities = new Map();
491
+ const destinations = new Map();
492
+ const violations = [];
493
+ for (let i = 0; i < ctx.intentTrades.length; i++) {
494
+ const trade = ctx.intentTrades[i];
495
+ const responseTrade = ctx.intentResponseTrades[i];
322
496
  const dstRealChainId = (0, subscription_to_chain_id_1.resolveChainId)(trade.dstChainId);
323
497
  // Safe skip: unknown subscriptionId for dstChainId is reported elsewhere
324
498
  // (validateTrades reports UnknownSubscriptionId for dstChainId)
325
499
  if (dstRealChainId === undefined)
326
500
  continue;
327
- set.add(buildChainTokenKey(trade.dstChainId, dstRealChainId, trade.dstChainTokenOut));
501
+ const destinationKey = buildChainTokenKey(trade.dstChainId, dstRealChainId, trade.dstChainTokenOut);
502
+ if (destinationKey === undefined) {
503
+ violations.push({
504
+ code: types_1.ViolationCode.RequestTradeAddressInvalid,
505
+ message: "request.trades[].dstChainTokenOut is not a valid address"
506
+ + " for dstChainId",
507
+ path: `request.trades[${(0, trade_grouping_1.getRequestTradeIndex)(ctx, i)}].dstChainTokenOut`,
508
+ expected: {
509
+ dstChainId: trade.dstChainId,
510
+ realChainId: dstRealChainId,
511
+ },
512
+ actual: trade.dstChainTokenOut,
513
+ });
514
+ }
515
+ else {
516
+ destinations.set(destinationKey, {
517
+ takeTokenChainId: trade.dstChainId,
518
+ takeTokenAddress: trade.dstChainTokenOut,
519
+ });
520
+ }
521
+ const pair = responseTrade === undefined
522
+ ? undefined
523
+ : resolveTakeTokenPairIdentity(trade, responseTrade);
524
+ if (pair !== undefined) {
525
+ identities.set(pair.key, {
526
+ fromTokenAddress: pair.fromTokenAddress,
527
+ takeTokenChainId: trade.dstChainId,
528
+ takeTokenAddress: pair.takeTokenAddress,
529
+ });
530
+ }
328
531
  // CRITICAL: same-chain Solana with native dst — API also emits a wSOL
329
532
  // takeToken[] entry alongside the native one because Solana DLN
330
533
  // settlement operates on SPL tokens. Both entries are legitimate and
@@ -332,27 +535,57 @@ function buildExpectedTakeTokenDestinations(ctx) {
332
535
  if (trade.srcChainId === trade.dstChainId
333
536
  && (0, address_1.getChainEngine)(dstRealChainId) === types_1.ChainEngine.Solana
334
537
  && (0, address_1.isNativeToken)(trade.dstChainTokenOut, dstRealChainId)) {
335
- set.add(buildChainTokenKey(trade.dstChainId, dstRealChainId, programs_1.SPL_NATIVE_MINT));
538
+ const wrappedDestinationKey = buildChainTokenKey(trade.dstChainId, dstRealChainId, programs_1.SPL_NATIVE_MINT);
539
+ if (wrappedDestinationKey !== undefined) {
540
+ destinations.set(wrappedDestinationKey, {
541
+ takeTokenChainId: trade.dstChainId,
542
+ takeTokenAddress: programs_1.SPL_NATIVE_MINT,
543
+ });
544
+ }
545
+ if (pair !== undefined) {
546
+ const wrappedIdentityKey = buildResolvedTakeTokenIdentityKey(pair.fromChainId, pair.fromTokenAddress, pair.takeChainId, programs_1.SPL_NATIVE_MINT);
547
+ if (wrappedIdentityKey !== undefined) {
548
+ identities.set(wrappedIdentityKey, {
549
+ fromTokenAddress: pair.fromTokenAddress,
550
+ takeTokenChainId: trade.dstChainId,
551
+ takeTokenAddress: programs_1.SPL_NATIVE_MINT,
552
+ });
553
+ }
554
+ }
336
555
  }
337
556
  }
338
- return set;
557
+ return { identities, destinations, violations };
339
558
  }
340
559
  function validateSingleTakeToken(intent, takeToken, group, expected, basePath) {
341
560
  const violations = [];
342
561
  const dstRealChainId = (0, subscription_to_chain_id_1.resolveChainId)(takeToken.takeTokenChainId);
343
- // Safe skip: unknown subscriptionId for dstChainId is reported elsewhere
344
- // (validateTrades reports UnknownSubscriptionId for dstChainId)
345
- if (dstRealChainId !== undefined) {
562
+ if (dstRealChainId === undefined) {
563
+ violations.push({
564
+ code: types_1.ViolationCode.IntentTakeTokenAddressMismatch,
565
+ message: "intent.takeToken[].takeTokenChainId is not a known"
566
+ + " destination subscriptionId",
567
+ path: `${basePath}.takeTokenChainId`,
568
+ expected: Array.from(expected.values()),
569
+ actual: {
570
+ takeTokenChainId: takeToken.takeTokenChainId,
571
+ takeTokenAddress: takeToken.takeTokenAddress,
572
+ },
573
+ });
574
+ }
575
+ else {
346
576
  const key = buildChainTokenKey(takeToken.takeTokenChainId, dstRealChainId, takeToken.takeTokenAddress);
347
- if (!expected.has(key)) {
577
+ if (key === undefined || !expected.has(key)) {
348
578
  violations.push({
349
579
  code: types_1.ViolationCode.IntentTakeTokenAddressMismatch,
350
580
  message: "intent.takeToken[] (takeTokenChainId, takeTokenAddress)"
351
581
  + " pair does not match any request (dstChainId,"
352
582
  + " dstChainTokenOut) pair in the trade group",
353
583
  path: basePath,
354
- expected: Array.from(expected),
355
- actual: key,
584
+ expected: Array.from(expected.values()),
585
+ actual: {
586
+ takeTokenChainId: takeToken.takeTokenChainId,
587
+ takeTokenAddress: takeToken.takeTokenAddress,
588
+ },
356
589
  });
357
590
  }
358
591
  }
@@ -361,11 +594,18 @@ function validateSingleTakeToken(intent, takeToken, group, expected, basePath) {
361
594
  }
362
595
  function validateTakeTokenFromAddress(intent, takeToken, group, basePath) {
363
596
  const isSameChain = takeToken.takeTokenChainId === group.srcChainId;
364
- const normalized = (0, address_1.normalizeAddress)(group.realChainId, takeToken.fromTokenAddress);
365
- const allowedSet = isSameChain
366
- ? new Set((intent.inputToken ?? []).map(t => (0, address_1.normalizeAddress)(group.realChainId, t.address)))
367
- : new Set((intent.giveToken ?? []).map(t => (0, address_1.normalizeAddress)(group.realChainId, t.giveTokenAddress)));
368
- if (!allowedSet.has(normalized)) {
597
+ const addressKey = (0, address_1.getAddressIdentityKey)(group.realChainId, takeToken.fromTokenAddress);
598
+ const allowedAddresses = isSameChain
599
+ ? (intent.inputToken ?? []).map(t => t.address)
600
+ : (intent.giveToken ?? []).map(t => t.giveTokenAddress);
601
+ const allowedByIdentity = new Map();
602
+ for (const address of allowedAddresses) {
603
+ const allowedAddressKey = (0, address_1.getAddressIdentityKey)(group.realChainId, address);
604
+ if (allowedAddressKey !== undefined) {
605
+ allowedByIdentity.set(allowedAddressKey, address);
606
+ }
607
+ }
608
+ if (addressKey === undefined || !allowedByIdentity.has(addressKey)) {
369
609
  return [{
370
610
  code: types_1.ViolationCode.IntentTakeTokenFromAddressInvalid,
371
611
  message: isSameChain
@@ -374,7 +614,7 @@ function validateTakeTokenFromAddress(intent, takeToken, group, basePath) {
374
614
  : "intent.takeToken[].fromTokenAddress must be present"
375
615
  + " in intent.giveToken[].giveTokenAddress for cross-chain trades",
376
616
  path: `${basePath}.fromTokenAddress`,
377
- expected: Array.from(allowedSet),
617
+ expected: Array.from(allowedByIdentity.values()),
378
618
  actual: takeToken.fromTokenAddress,
379
619
  }];
380
620
  }