@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.
- package/README.md +120 -11
- package/dist/cjs/graylists/evm/token-permit-metadata.js +7 -1
- package/dist/cjs/graylists/evm/token-permit-metadata.js.map +1 -1
- package/dist/cjs/helpers/address.js +139 -63
- package/dist/cjs/helpers/address.js.map +1 -1
- package/dist/cjs/helpers/amount.js +8 -0
- package/dist/cjs/helpers/amount.js.map +1 -0
- package/dist/cjs/helpers/evm-dln-hook-pack.js +16 -4
- package/dist/cjs/helpers/evm-dln-hook-pack.js.map +1 -1
- package/dist/cjs/helpers/evm-gas-compensation-tx.js +11 -26
- package/dist/cjs/helpers/evm-gas-compensation-tx.js.map +1 -1
- package/dist/cjs/helpers/evm-intent-id-abi.js +8 -1
- package/dist/cjs/helpers/evm-intent-id-abi.js.map +1 -1
- package/dist/cjs/helpers/evm-trade-amounts.js +11 -3
- package/dist/cjs/helpers/evm-trade-amounts.js.map +1 -1
- package/dist/cjs/helpers/expected-delegate.js +2 -6
- package/dist/cjs/helpers/expected-delegate.js.map +1 -1
- package/dist/cjs/helpers/gas-compensation.js +8 -4
- package/dist/cjs/helpers/gas-compensation.js.map +1 -1
- package/dist/cjs/helpers/interactions.js +4 -20
- package/dist/cjs/helpers/interactions.js.map +1 -1
- package/dist/cjs/helpers/synthetic-native-wrap.js +3 -2
- package/dist/cjs/helpers/synthetic-native-wrap.js.map +1 -1
- package/dist/cjs/types/enums.js +1 -0
- package/dist/cjs/types/enums.js.map +1 -1
- package/dist/cjs/types/request.js +15 -2
- package/dist/cjs/types/request.js.map +1 -1
- package/dist/cjs/types/response.js +2 -1
- package/dist/cjs/types/response.js.map +1 -1
- package/dist/cjs/types/validation.js +21 -3
- package/dist/cjs/types/validation.js.map +1 -1
- package/dist/cjs/validations/compensation-hook.js +4 -5
- package/dist/cjs/validations/compensation-hook.js.map +1 -1
- package/dist/cjs/validations/cost-token.js +9 -1
- package/dist/cjs/validations/cost-token.js.map +1 -1
- package/dist/cjs/validations/echo-fields.js +18 -0
- package/dist/cjs/validations/echo-fields.js.map +1 -1
- package/dist/cjs/validations/evm-caveats.js +3 -0
- package/dist/cjs/validations/evm-caveats.js.map +1 -1
- package/dist/cjs/validations/evm-on-chain-intent.js +150 -0
- package/dist/cjs/validations/evm-on-chain-intent.js.map +1 -0
- package/dist/cjs/validations/evm-op-budget.js +23 -3
- package/dist/cjs/validations/evm-op-budget.js.map +1 -1
- package/dist/cjs/validations/evm-op-hook.js +6 -6
- package/dist/cjs/validations/evm-op-hook.js.map +1 -1
- package/dist/cjs/validations/evm-permit.js +17 -6
- package/dist/cjs/validations/evm-permit.js.map +1 -1
- package/dist/cjs/validations/evm-permit2.js +14 -6
- package/dist/cjs/validations/evm-permit2.js.map +1 -1
- package/dist/cjs/validations/evm-required-actions.js +26 -7
- package/dist/cjs/validations/evm-required-actions.js.map +1 -1
- package/dist/cjs/validations/evm-sign712-metamask.js +3 -126
- package/dist/cjs/validations/evm-sign712-metamask.js.map +1 -1
- package/dist/cjs/validations/evm-sign712-shared.js +127 -0
- package/dist/cjs/validations/evm-sign712-shared.js.map +1 -1
- package/dist/cjs/validations/evm-sign712.js +18 -2
- package/dist/cjs/validations/evm-sign712.js.map +1 -1
- package/dist/cjs/validations/evm-sign7702.js +2 -0
- package/dist/cjs/validations/evm-sign7702.js.map +1 -1
- package/dist/cjs/validations/evm-transaction.js +7 -1
- package/dist/cjs/validations/evm-transaction.js.map +1 -1
- package/dist/cjs/validations/external-call.js +9 -20
- package/dist/cjs/validations/external-call.js.map +1 -1
- package/dist/cjs/validations/hook-version.js +35 -0
- package/dist/cjs/validations/hook-version.js.map +1 -0
- package/dist/cjs/validations/hooks-common.js +74 -199
- package/dist/cjs/validations/hooks-common.js.map +1 -1
- package/dist/cjs/validations/intent-destinations.js +52 -50
- package/dist/cjs/validations/intent-destinations.js.map +1 -1
- package/dist/cjs/validations/intent-tokens.js +319 -79
- package/dist/cjs/validations/intent-tokens.js.map +1 -1
- package/dist/cjs/validations/intent.js +34 -26
- package/dist/cjs/validations/intent.js.map +1 -1
- package/dist/cjs/validations/max-reserved.js +54 -0
- package/dist/cjs/validations/max-reserved.js.map +1 -0
- package/dist/cjs/validations/on-chain-intent-submission.js +70 -0
- package/dist/cjs/validations/on-chain-intent-submission.js.map +1 -0
- package/dist/cjs/validations/ptp.js +9 -6
- package/dist/cjs/validations/ptp.js.map +1 -1
- package/dist/cjs/validations/refill.js +11 -5
- package/dist/cjs/validations/refill.js.map +1 -1
- package/dist/cjs/validations/solana-hook.js +12 -64
- package/dist/cjs/validations/solana-hook.js.map +1 -1
- package/dist/cjs/validations/solana-op-budget.js +1 -1
- package/dist/cjs/validations/solana-op-budget.js.map +1 -1
- package/dist/cjs/validations/solana-op-wrap.js +29 -47
- package/dist/cjs/validations/solana-op-wrap.js.map +1 -1
- package/dist/cjs/validations/solana-required-actions.js +2 -2
- package/dist/cjs/validations/solana-required-actions.js.map +1 -1
- package/dist/cjs/validations/solana-transaction.js +4 -53
- package/dist/cjs/validations/solana-transaction.js.map +1 -1
- package/dist/cjs/validations/total-dst-amount.js +19 -3
- package/dist/cjs/validations/total-dst-amount.js.map +1 -1
- package/dist/cjs/validations/trade-grouping.js +35 -9
- package/dist/cjs/validations/trade-grouping.js.map +1 -1
- package/dist/cjs/validations/trade-matching.js +1 -1
- package/dist/cjs/validations/trade-matching.js.map +1 -1
- package/dist/cjs/validations/trades.js +29 -9
- package/dist/cjs/validations/trades.js.map +1 -1
- package/dist/cjs/validator.js +28 -17
- package/dist/cjs/validator.js.map +1 -1
- package/dist/cjs/whitelists/environment-config.js +9 -22
- package/dist/cjs/whitelists/environment-config.js.map +1 -1
- package/dist/cjs/whitelists/types.js +2 -5
- package/dist/cjs/whitelists/types.js.map +1 -1
- package/dist/esm/graylists/evm/token-permit-metadata.js +7 -1
- package/dist/esm/graylists/evm/token-permit-metadata.js.map +1 -1
- package/dist/esm/helpers/address.js +133 -61
- package/dist/esm/helpers/address.js.map +1 -1
- package/dist/esm/helpers/amount.js +5 -0
- package/dist/esm/helpers/amount.js.map +1 -0
- package/dist/esm/helpers/evm-dln-hook-pack.js +15 -4
- package/dist/esm/helpers/evm-dln-hook-pack.js.map +1 -1
- package/dist/esm/helpers/evm-gas-compensation-tx.js +12 -27
- package/dist/esm/helpers/evm-gas-compensation-tx.js.map +1 -1
- package/dist/esm/helpers/evm-intent-id-abi.js +7 -0
- package/dist/esm/helpers/evm-intent-id-abi.js.map +1 -1
- package/dist/esm/helpers/evm-trade-amounts.js +12 -4
- package/dist/esm/helpers/evm-trade-amounts.js.map +1 -1
- package/dist/esm/helpers/expected-delegate.js +2 -6
- package/dist/esm/helpers/expected-delegate.js.map +1 -1
- package/dist/esm/helpers/gas-compensation.js +9 -5
- package/dist/esm/helpers/gas-compensation.js.map +1 -1
- package/dist/esm/helpers/interactions.js +5 -21
- package/dist/esm/helpers/interactions.js.map +1 -1
- package/dist/esm/helpers/synthetic-native-wrap.js +4 -3
- package/dist/esm/helpers/synthetic-native-wrap.js.map +1 -1
- package/dist/esm/types/enums.js +1 -0
- package/dist/esm/types/enums.js.map +1 -1
- package/dist/esm/types/request.js +15 -2
- package/dist/esm/types/request.js.map +1 -1
- package/dist/esm/types/response.js +3 -2
- package/dist/esm/types/response.js.map +1 -1
- package/dist/esm/types/validation.js +21 -3
- package/dist/esm/types/validation.js.map +1 -1
- package/dist/esm/validations/compensation-hook.js +4 -5
- package/dist/esm/validations/compensation-hook.js.map +1 -1
- package/dist/esm/validations/cost-token.js +9 -1
- package/dist/esm/validations/cost-token.js.map +1 -1
- package/dist/esm/validations/echo-fields.js +18 -0
- package/dist/esm/validations/echo-fields.js.map +1 -1
- package/dist/esm/validations/evm-caveats.js +3 -0
- package/dist/esm/validations/evm-caveats.js.map +1 -1
- package/dist/esm/validations/evm-on-chain-intent.js +147 -0
- package/dist/esm/validations/evm-on-chain-intent.js.map +1 -0
- package/dist/esm/validations/evm-op-budget.js +25 -5
- package/dist/esm/validations/evm-op-budget.js.map +1 -1
- package/dist/esm/validations/evm-op-hook.js +8 -8
- package/dist/esm/validations/evm-op-hook.js.map +1 -1
- package/dist/esm/validations/evm-permit.js +18 -7
- package/dist/esm/validations/evm-permit.js.map +1 -1
- package/dist/esm/validations/evm-permit2.js +15 -7
- package/dist/esm/validations/evm-permit2.js.map +1 -1
- package/dist/esm/validations/evm-required-actions.js +28 -9
- package/dist/esm/validations/evm-required-actions.js.map +1 -1
- package/dist/esm/validations/evm-sign712-metamask.js +4 -126
- package/dist/esm/validations/evm-sign712-metamask.js.map +1 -1
- package/dist/esm/validations/evm-sign712-shared.js +126 -1
- package/dist/esm/validations/evm-sign712-shared.js.map +1 -1
- package/dist/esm/validations/evm-sign712.js +17 -2
- package/dist/esm/validations/evm-sign712.js.map +1 -1
- package/dist/esm/validations/evm-sign7702.js +2 -0
- package/dist/esm/validations/evm-sign7702.js.map +1 -1
- package/dist/esm/validations/evm-transaction.js +7 -1
- package/dist/esm/validations/evm-transaction.js.map +1 -1
- package/dist/esm/validations/external-call.js +10 -21
- package/dist/esm/validations/external-call.js.map +1 -1
- package/dist/esm/validations/hook-version.js +31 -0
- package/dist/esm/validations/hook-version.js.map +1 -0
- package/dist/esm/validations/hooks-common.js +75 -200
- package/dist/esm/validations/hooks-common.js.map +1 -1
- package/dist/esm/validations/intent-destinations.js +53 -51
- package/dist/esm/validations/intent-destinations.js.map +1 -1
- package/dist/esm/validations/intent-tokens.js +320 -80
- package/dist/esm/validations/intent-tokens.js.map +1 -1
- package/dist/esm/validations/intent.js +34 -26
- package/dist/esm/validations/intent.js.map +1 -1
- package/dist/esm/validations/max-reserved.js +51 -0
- package/dist/esm/validations/max-reserved.js.map +1 -0
- package/dist/esm/validations/on-chain-intent-submission.js +67 -0
- package/dist/esm/validations/on-chain-intent-submission.js.map +1 -0
- package/dist/esm/validations/ptp.js +9 -6
- package/dist/esm/validations/ptp.js.map +1 -1
- package/dist/esm/validations/refill.js +11 -5
- package/dist/esm/validations/refill.js.map +1 -1
- package/dist/esm/validations/solana-hook.js +12 -64
- package/dist/esm/validations/solana-hook.js.map +1 -1
- package/dist/esm/validations/solana-op-budget.js +1 -1
- package/dist/esm/validations/solana-op-budget.js.map +1 -1
- package/dist/esm/validations/solana-op-wrap.js +30 -48
- package/dist/esm/validations/solana-op-wrap.js.map +1 -1
- package/dist/esm/validations/solana-required-actions.js +2 -2
- package/dist/esm/validations/solana-required-actions.js.map +1 -1
- package/dist/esm/validations/solana-transaction.js +4 -53
- package/dist/esm/validations/solana-transaction.js.map +1 -1
- package/dist/esm/validations/total-dst-amount.js +20 -4
- package/dist/esm/validations/total-dst-amount.js.map +1 -1
- package/dist/esm/validations/trade-grouping.js +35 -10
- package/dist/esm/validations/trade-grouping.js.map +1 -1
- package/dist/esm/validations/trade-matching.js +1 -1
- package/dist/esm/validations/trade-matching.js.map +1 -1
- package/dist/esm/validations/trades.js +29 -9
- package/dist/esm/validations/trades.js.map +1 -1
- package/dist/esm/validator.js +28 -17
- package/dist/esm/validator.js.map +1 -1
- package/dist/esm/whitelists/environment-config.js +8 -21
- package/dist/esm/whitelists/environment-config.js.map +1 -1
- package/dist/esm/whitelists/types.js +3 -6
- package/dist/esm/whitelists/types.js.map +1 -1
- package/dist/types/graylists/evm/token-permit-metadata.d.ts.map +1 -1
- package/dist/types/helpers/address.d.ts +9 -5
- package/dist/types/helpers/address.d.ts.map +1 -1
- package/dist/types/helpers/amount.d.ts +2 -0
- package/dist/types/helpers/amount.d.ts.map +1 -0
- package/dist/types/helpers/evm-dln-hook-pack.d.ts +1 -0
- package/dist/types/helpers/evm-dln-hook-pack.d.ts.map +1 -1
- package/dist/types/helpers/evm-gas-compensation-tx.d.ts +1 -3
- package/dist/types/helpers/evm-gas-compensation-tx.d.ts.map +1 -1
- package/dist/types/helpers/evm-intent-id-abi.d.ts +111 -0
- package/dist/types/helpers/evm-intent-id-abi.d.ts.map +1 -1
- package/dist/types/helpers/evm-trade-amounts.d.ts.map +1 -1
- package/dist/types/helpers/expected-delegate.d.ts.map +1 -1
- package/dist/types/helpers/gas-compensation.d.ts.map +1 -1
- package/dist/types/helpers/interactions.d.ts.map +1 -1
- package/dist/types/helpers/synthetic-native-wrap.d.ts.map +1 -1
- package/dist/types/types/enums.d.ts +1 -0
- package/dist/types/types/enums.d.ts.map +1 -1
- package/dist/types/types/request.d.ts +32 -23
- package/dist/types/types/request.d.ts.map +1 -1
- package/dist/types/types/response.d.ts +10 -42
- package/dist/types/types/response.d.ts.map +1 -1
- package/dist/types/types/validation.d.ts +19 -4
- package/dist/types/types/validation.d.ts.map +1 -1
- package/dist/types/validations/compensation-hook.d.ts +1 -2
- package/dist/types/validations/compensation-hook.d.ts.map +1 -1
- package/dist/types/validations/cost-token.d.ts.map +1 -1
- package/dist/types/validations/echo-fields.d.ts.map +1 -1
- package/dist/types/validations/evm-caveats.d.ts.map +1 -1
- package/dist/types/validations/evm-on-chain-intent.d.ts +4 -0
- package/dist/types/validations/evm-on-chain-intent.d.ts.map +1 -0
- package/dist/types/validations/evm-op-budget.d.ts.map +1 -1
- package/dist/types/validations/evm-op-hook.d.ts.map +1 -1
- package/dist/types/validations/evm-permit.d.ts.map +1 -1
- package/dist/types/validations/evm-permit2.d.ts.map +1 -1
- package/dist/types/validations/evm-required-actions.d.ts.map +1 -1
- package/dist/types/validations/evm-sign712-metamask.d.ts +0 -11
- package/dist/types/validations/evm-sign712-metamask.d.ts.map +1 -1
- package/dist/types/validations/evm-sign712-shared.d.ts +12 -0
- package/dist/types/validations/evm-sign712-shared.d.ts.map +1 -1
- package/dist/types/validations/evm-sign712.d.ts +1 -0
- package/dist/types/validations/evm-sign712.d.ts.map +1 -1
- package/dist/types/validations/evm-sign7702.d.ts.map +1 -1
- package/dist/types/validations/evm-transaction.d.ts +1 -1
- package/dist/types/validations/evm-transaction.d.ts.map +1 -1
- package/dist/types/validations/external-call.d.ts.map +1 -1
- package/dist/types/validations/hook-version.d.ts +7 -0
- package/dist/types/validations/hook-version.d.ts.map +1 -0
- package/dist/types/validations/hooks-common.d.ts +1 -4
- package/dist/types/validations/hooks-common.d.ts.map +1 -1
- package/dist/types/validations/intent-destinations.d.ts.map +1 -1
- package/dist/types/validations/intent-tokens.d.ts.map +1 -1
- package/dist/types/validations/max-reserved.d.ts +3 -0
- package/dist/types/validations/max-reserved.d.ts.map +1 -0
- package/dist/types/validations/on-chain-intent-submission.d.ts +3 -0
- package/dist/types/validations/on-chain-intent-submission.d.ts.map +1 -0
- package/dist/types/validations/ptp.d.ts.map +1 -1
- package/dist/types/validations/refill.d.ts.map +1 -1
- package/dist/types/validations/solana-hook.d.ts +1 -2
- package/dist/types/validations/solana-hook.d.ts.map +1 -1
- package/dist/types/validations/solana-op-wrap.d.ts +2 -2
- package/dist/types/validations/solana-op-wrap.d.ts.map +1 -1
- package/dist/types/validations/solana-required-actions.d.ts.map +1 -1
- package/dist/types/validations/solana-transaction.d.ts +3 -3
- package/dist/types/validations/solana-transaction.d.ts.map +1 -1
- package/dist/types/validations/total-dst-amount.d.ts.map +1 -1
- package/dist/types/validations/trade-grouping.d.ts +1 -0
- package/dist/types/validations/trade-grouping.d.ts.map +1 -1
- package/dist/types/validations/trades.d.ts.map +1 -1
- package/dist/types/validator.d.ts.map +1 -1
- package/dist/types/whitelists/environment-config.d.ts +0 -1
- package/dist/types/whitelists/environment-config.d.ts.map +1 -1
- package/dist/types/whitelists/types.d.ts +0 -1
- package/dist/types/whitelists/types.d.ts.map +1 -1
- package/package.json +5 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ViolationCode, ChainEngine, } from "../types/index.js";
|
|
2
|
-
import {
|
|
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
|
|
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[${
|
|
36
|
+
path: `request.trades[${getRequestTradeIndex(ctx, i)}].srcChainTokenIn`,
|
|
35
37
|
actual: trade.srcChainTokenIn,
|
|
36
38
|
});
|
|
37
39
|
continue;
|
|
38
40
|
}
|
|
39
|
-
|
|
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 <
|
|
54
|
-
const
|
|
55
|
-
|
|
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(
|
|
94
|
+
firstOccurrence.set(addressKey, i);
|
|
68
95
|
}
|
|
69
96
|
}
|
|
70
|
-
for (const
|
|
71
|
-
if (!firstOccurrence.has(
|
|
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:
|
|
104
|
+
expected: expectedAddress,
|
|
78
105
|
});
|
|
79
106
|
}
|
|
80
107
|
}
|
|
81
108
|
// Validate each unique entry (duplicates were already reported above)
|
|
82
|
-
for (const [
|
|
83
|
-
if (!expectedTokens.has(
|
|
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,
|
|
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,
|
|
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
|
-
// `
|
|
146
|
+
// `resolveIntentInputTokens`.
|
|
120
147
|
if (resolved === undefined)
|
|
121
148
|
return false;
|
|
122
|
-
|
|
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
|
|
220
|
+
const expectedEngine = getChainEngine(group.realChainId);
|
|
193
221
|
for (let i = 0; i < giveTokens.length; i++) {
|
|
194
|
-
const
|
|
195
|
-
if (!allowed.has(
|
|
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
|
-
|
|
210
|
-
if (actualFormat !== expectedFormat) {
|
|
237
|
+
if (!isAddressRepresentationValidForChain(group.realChainId, giveTokens[i].giveTokenAddress)) {
|
|
211
238
|
violations.push({
|
|
212
239
|
code: ViolationCode.IntentGiveTokenAddressFormatMismatch,
|
|
213
|
-
message:
|
|
214
|
-
+ `
|
|
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
|
|
230
|
-
const
|
|
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
|
-
//
|
|
267
|
+
// `resolveIntentInputTokens`.
|
|
237
268
|
if (resolved === undefined)
|
|
238
269
|
continue;
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
-
|
|
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
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
282
|
-
if (!
|
|
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 (
|
|
286
|
-
+ " missing from
|
|
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
|
|
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:
|
|
299
|
-
// to compare takeToken entries as a set.
|
|
300
|
-
//
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
317
|
-
const
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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:
|
|
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
|
|
362
|
-
const
|
|
363
|
-
?
|
|
364
|
-
:
|
|
365
|
-
|
|
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(
|
|
614
|
+
expected: Array.from(allowedByIdentity.values()),
|
|
375
615
|
actual: takeToken.fromTokenAddress,
|
|
376
616
|
}];
|
|
377
617
|
}
|