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