@vultisig/core-chain 2.35.1 → 2.36.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 (29) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/chains/sui/listAllCoins.d.ts.map +1 -1
  3. package/dist/chains/sui/listAllCoins.js +8 -0
  4. package/dist/chains/sui/listAllCoins.js.map +1 -1
  5. package/dist/swap/general/knownAggregatorRouters.d.ts +40 -22
  6. package/dist/swap/general/knownAggregatorRouters.d.ts.map +1 -1
  7. package/dist/swap/general/knownAggregatorRouters.js +147 -54
  8. package/dist/swap/general/knownAggregatorRouters.js.map +1 -1
  9. package/dist/swap/general/lifi/api/getLifiSwapQuote.d.ts.map +1 -1
  10. package/dist/swap/general/lifi/api/getLifiSwapQuote.js +15 -48
  11. package/dist/swap/general/lifi/api/getLifiSwapQuote.js.map +1 -1
  12. package/dist/swap/general/lifi/api/lifiSwapFeeChain.d.ts +4 -0
  13. package/dist/swap/general/lifi/api/lifiSwapFeeChain.d.ts.map +1 -0
  14. package/dist/swap/general/lifi/api/lifiSwapFeeChain.js +19 -0
  15. package/dist/swap/general/lifi/api/lifiSwapFeeChain.js.map +1 -0
  16. package/dist/swap/general/swapkit/api/getSwapKitQuote.d.ts.map +1 -1
  17. package/dist/swap/general/swapkit/api/getSwapKitQuote.js +24 -44
  18. package/dist/swap/general/swapkit/api/getSwapKitQuote.js.map +1 -1
  19. package/dist/swap/quote/SwapQuote.d.ts +12 -0
  20. package/dist/swap/quote/SwapQuote.d.ts.map +1 -1
  21. package/dist/swap/quote/findSwapQuote.d.ts +9 -2
  22. package/dist/swap/quote/findSwapQuote.d.ts.map +1 -1
  23. package/dist/swap/quote/findSwapQuote.js +32 -2
  24. package/dist/swap/quote/findSwapQuote.js.map +1 -1
  25. package/dist/swap/quote/getSwapQuoteSafetyFingerprint.d.ts +20 -0
  26. package/dist/swap/quote/getSwapQuoteSafetyFingerprint.d.ts.map +1 -0
  27. package/dist/swap/quote/getSwapQuoteSafetyFingerprint.js +67 -0
  28. package/dist/swap/quote/getSwapQuoteSafetyFingerprint.js.map +1 -0
  29. package/package.json +11 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @vultisig/core-chain
2
2
 
3
+ ## 2.36.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1830](https://github.com/vultisig/vultisig-sdk/pull/1830) [`1fd27b9`](https://github.com/vultisig/vultisig-sdk/commit/1fd27b9103b75a13191f173ebeed98288d8c16b0) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Fail closed when LI.FI returns a destination outside its official chain-scoped Diamond deployments. Accept that Diamond as LI.FI's deterministic approval-spender fast path, while requiring an independent benign Blockaid verdict for any route-dependent spender that differs from it. Require the same independent verdicts for SwapKit EVM transaction destinations and approval spenders, while retaining response-local target-address binding as defense in depth.
8
+
9
+ **Consumer-visible behavior changes:**
10
+
11
+ - **New runtime throws on the signing path.** A LI.FI destination outside the official Diamond, a distinct LI.FI approval spender that doesn't clear an independent Blockaid check, or a SwapKit destination/approval-spender that doesn't clear the same check now throws during quote construction and co-signer signing-input construction. A consumer on a caret range picks this up automatically.
12
+ - **Dynamic LI.FI approval spenders and SwapKit addresses add a live third-party network call (Blockaid) during MPC signing**, per co-signer. The official LI.FI Diamond remains an offline fast path. A slow or unreachable Blockaid response can stall a signing ceremony for up to the shared `queryUrl` timeout (20s default) before throwing.
13
+
14
+ - [#1811](https://github.com/vultisig/vultisig-sdk/pull/1811) [`003ee98`](https://github.com/vultisig/vultisig-sdk/commit/003ee98728f3318eb2de661209f164b449150810) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Bind raw swap quotes to their exact coin identities, requested source amount, absolute expiry, and returned transaction, then reject missing, stale, mismatched, or mutated quotes in the vault-free swap preparation path before any payload construction. General quotes remain preparation-valid for five minutes while SDK presentation continues to recommend refreshing after 60 seconds, and vault-free callers can catch `SwapQuoteExpiredError` to requote.
15
+
16
+ Consumers must pass the live bound quote returned by `findSwapQuote` or `getSwapQuote` into preparation instead of constructing or persisting an unbound raw quote. Structured cloning is supported; JSON round-tripping is not because it loses required runtime value types such as `bigint`.
17
+
18
+ ### Patch Changes
19
+
20
+ - [#1883](https://github.com/vultisig/vultisig-sdk/pull/1883) [`44f0ecb`](https://github.com/vultisig/vultisig-sdk/commit/44f0ecbdf10b07789341c0279041d3a1e37d46a0) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Share LI.FI fee-chain fallback logic between the core and React Native quote paths.
21
+
22
+ - [#1886](https://github.com/vultisig/vultisig-sdk/pull/1886) [`eb25508`](https://github.com/vultisig/vultisig-sdk/commit/eb25508eadfde14ee62470e64d8c2b590627fd76) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Fail closed when Sui pagination reports more data without a usable cursor, preventing partial coin sets or portfolios from being returned as complete.
23
+
3
24
  ## 2.35.1
4
25
 
5
26
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"listAllCoins.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/chain/chains/sui/listAllCoins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,MAAM,CAAA;AAIlC,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,GAAG,SAAS,EAAE,UAAU,MAAM,KAAG,MAChC,CAAA;AAErD,KAAK,oBAAoB,GAAG;IAC1B,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAU,6BAA6B,oBAAoB,KAAG,OAAO,CAAC,aAAa,EAAE,CA4BhH,CAAA"}
1
+ {"version":3,"file":"listAllCoins.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/chain/chains/sui/listAllCoins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,MAAM,CAAA;AAIlC,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,GAAG,SAAS,EAAE,UAAU,MAAM,KAAG,MAChC,CAAA;AAErD,KAAK,oBAAoB,GAAG;IAC1B,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAU,6BAA6B,oBAAoB,KAAG,OAAO,CAAC,aAAa,EAAE,CAuChH,CAAA"}
@@ -33,6 +33,14 @@ export const listAllSuiCoins = async ({ client, owner, coinType }) => {
33
33
  balance: object.balance,
34
34
  });
35
35
  }
36
+ // `hasNextPage: true` without a cursor cannot be followed. Treat every
37
+ // falsy cursor as malformed because the gRPC adapter can turn an empty page
38
+ // token into `cursor: ''`, while GraphQL can return a null end cursor.
39
+ // Returning the coins collected so far would make a truncated set look
40
+ // complete and can surface later as a false insufficient-balance error.
41
+ if (page.hasNextPage && !page.cursor) {
42
+ throw new Error(`listAllSuiCoins: page ${pages + 1} for ${owner} (${coinType}) reports hasNextPage with no cursor — refusing to build a send from a truncated coin set`);
43
+ }
36
44
  cursor = page.hasNextPage ? page.cursor : null;
37
45
  if (++pages >= maxSuiCoinPages && cursor) {
38
46
  throw new Error(`listAllSuiCoins: exceeded ${maxSuiCoinPages} pages for ${owner} (${coinType}) — refusing to build a send from a possibly-truncated or looping coin set`);
@@ -1 +1 @@
1
- {"version":3,"file":"listAllCoins.js","sourceRoot":"","sources":["../../../../../../packages/core/chain/chains/sui/listAllCoins.ts"],"names":[],"mappings":"AAmBA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,MAAM,eAAe,GAAG,qBAAqB,CAAA;AAE7C,2DAA2D;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,UAA8B,EAAE,QAAgB,EAAU,EAAE,CAC5F,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAA;AAQrD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAwB,EAA4B,EAAE;IACnH,MAAM,KAAK,GAAoB,EAAE,CAAA;IACjC,IAAI,MAAM,GAA8B,SAAS,CAAA;IACjD,IAAI,KAAK,GAAG,CAAC,CAAA;IAEb,GAAG,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QAEhE,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CAAC;gBACT,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC;gBAClD,YAAY,EAAE,MAAM,CAAC,QAAQ;gBAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC,CAAA;QACJ,CAAC;QAED,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;QAE9C,IAAI,EAAE,KAAK,IAAI,eAAe,IAAI,MAAM,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,6BAA6B,eAAe,cAAc,KAAK,KAAK,QAAQ,4EAA4E,CACzJ,CAAA;QACH,CAAC;IACH,CAAC,QAAQ,MAAM,EAAC;IAEhB,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
1
+ {"version":3,"file":"listAllCoins.js","sourceRoot":"","sources":["../../../../../../packages/core/chain/chains/sui/listAllCoins.ts"],"names":[],"mappings":"AAmBA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,MAAM,eAAe,GAAG,qBAAqB,CAAA;AAE7C,2DAA2D;AAC3D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,UAA8B,EAAE,QAAgB,EAAU,EAAE,CAC5F,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAA;AAQrD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAwB,EAA4B,EAAE;IACnH,MAAM,KAAK,GAAoB,EAAE,CAAA;IACjC,IAAI,MAAM,GAA8B,SAAS,CAAA;IACjD,IAAI,KAAK,GAAG,CAAC,CAAA;IAEb,GAAG,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QAEhE,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CAAC;gBACT,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC;gBAClD,YAAY,EAAE,MAAM,CAAC,QAAQ;gBAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC,CAAA;QACJ,CAAC;QAED,uEAAuE;QACvE,4EAA4E;QAC5E,uEAAuE;QACvE,uEAAuE;QACvE,wEAAwE;QACxE,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,yBAAyB,KAAK,GAAG,CAAC,QAAQ,KAAK,KAAK,QAAQ,2FAA2F,CACxJ,CAAA;QACH,CAAC;QAED,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;QAE9C,IAAI,EAAE,KAAK,IAAI,eAAe,IAAI,MAAM,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,6BAA6B,eAAe,cAAc,KAAK,KAAK,QAAQ,4EAA4E,CACzJ,CAAA;QACH,CAAC;IACH,CAAC,QAAQ,MAAM,EAAC;IAEhB,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
@@ -1,12 +1,11 @@
1
1
  import { Chain } from '../../Chain.js';
2
- export type EnforcedRouterProvider = '1inch' | 'kyber' | 'cowswap';
2
+ export type EnforcedRouterProvider = '1inch' | 'kyber' | 'cowswap' | 'li.fi';
3
3
  /**
4
4
  * Signing-path re-assert (sdk#1358): the same allow-list check as {@link assertKnownAggregatorRouter},
5
5
  * but keyed off an arbitrary provider STRING (the value carried in KeysignSwapPayload.general.provider,
6
- * which is a plain `string`). Enforced providers (1inch/kyber/cowswap) fail closed; the small closed set
7
- * of genuinely unenforceable providers (li.fi/swapkit, plus the legacy `''` unattributed provider - see
8
- * RECOGNIZED_UNENFORCED_PROVIDERS) fall through to the same log-only path they take at quote construction;
9
- * anything else is rejected outright (sdk#1457, see below).
6
+ * which is a plain `string`). Fixed-router providers fail closed against their allowlists, SwapKit
7
+ * fails closed against an independent Blockaid reputation verdict, and only the legacy `''`
8
+ * unattributed provider remains log-only. Anything else is rejected outright (sdk#1457, see below).
10
9
  *
11
10
  * WHY THIS EXISTS SEPARATELY FROM quote construction: a compromised initiator (or server composing the
12
11
  * intent) can hand a co-signer a KeysignPayload whose swapPayload.quote.tx.to was NEVER run through the
@@ -23,23 +22,35 @@ export type EnforcedRouterProvider = '1inch' | 'kyber' | 'cowswap';
23
22
  * enforcement) is a real, closable gap - not just a theoretical one.
24
23
  *
25
24
  * sdk#1457 FIX: two structural improvements that need no proto change. (1) CowSwap is now enforced -
26
- * unlike li.fi/swapkit it settles through ONE fixed, deterministic contract (the GPv2VaultRelayer, see
25
+ * unlike swapkit it settles through ONE fixed, deterministic contract (the GPv2VaultRelayer, see
27
26
  * assertKnownAggregatorRouter), so its destination is exactly as allow-listable as 1inch/Kyber's; a
28
27
  * payload can no longer relabel itself 'cowswap' to dodge a router check the way it previously could. (2)
29
28
  * the log-only fallback is now a CLOSED list of the provider values the codebase legitimately produces
30
- * (li.fi/swapkit, plus the legacy `''` unattributed provider) - a `provider` string outside every known
29
+ * (at that time li.fi/swapkit, plus the legacy `''` unattributed provider) - a `provider` string outside every known
31
30
  * value (enforced or unenforced) is unrecognized and REJECTED, not silently passed through. Together
32
- * these shrink "relabel to escape enforcement" from "any string at all" down to exactly li.fi, swapkit,
33
- * and the legacy `''`, which remain unenforceable by address because they legitimately route through
34
- * many different contracts by design (see the file doc comment) - closing that residual gap needs the
35
- * provider identity to be a trusted wire discriminant instead of a free string, i.e. a proto oneof case
36
- * per provider (tracked in sdk#1457, not attempted here: a schema change on the shared commondata proto
37
- * is a cross-repo, cross-consumer change every native client (iOS/Android/Windows) also builds against).
31
+ * these originally shrank "relabel to escape enforcement" from "any string at all" to that closed set.
32
+ * sdk#1458 subsequently removes li.fi from the residual gap using its official Diamond deployments
33
+ * and applies an independent Blockaid reputation check to SwapKit destinations on both quote and
34
+ * co-signer paths. The legacy unattributed value remains the only log-only compatibility case.
38
35
  *
39
36
  * This guard remains MONOTONIC beyond that residual gap: it only ever THROWS (rejects) or no-ops - it
40
37
  * never makes anything signable that wasn't already.
41
38
  */
42
39
  export declare function assertKnownAggregatorRouterOnSigningPath(provider: string, address: string, chain: Chain): void;
40
+ /**
41
+ * Independent trust boundary for dynamic SwapKit EVM addresses. SwapKit's `tx.to`,
42
+ * `targetAddress`, and optional approval transaction all come from the same `/v3/swap`
43
+ * response, so equality between them cannot establish destination safety when that response
44
+ * is compromised.
45
+ */
46
+ export declare function assertSwapKitAddressReputation(address: string, chain: Chain, role?: string): Promise<void>;
47
+ /**
48
+ * LI.FI documents `estimate.approvalAddress` as route-dependent and explicitly warns consumers
49
+ * not to hardcode it. The official Diamond is safe without a network dependency; a distinct
50
+ * spender is accepted only after an independent benign Blockaid verdict. This preserves valid
51
+ * inner-executor routes without trusting the quote response to authorize its own spender.
52
+ */
53
+ export declare function assertLifiApprovalAddress(address: string, chain: Chain): Promise<void>;
43
54
  /**
44
55
  * Signing-path approval-spender bind (sdk#1358 review follow-up, requested by neavra). The
45
56
  * follow-on to {@link assertKnownAggregatorRouterOnSigningPath}: that guard validates the swap-leg
@@ -49,12 +60,14 @@ export declare function assertKnownAggregatorRouterOnSigningPath(provider: strin
49
60
  * genuine 1inch/kyber `tx.to` yet still carry an approve granting an ATTACKER an allowance over the
50
61
  * user's token (a classic approval-drain the co-signer would otherwise sign blind).
51
62
  *
52
- * On the initiator these coincide by construction (build.ts sets the approve spender to
53
- * `getSwapDestinationAddress` === `tx.to`), so this only ever fires on a hand-built/tampered payload.
54
- * Enforced providers (1inch/kyber/cowswap) MUST have `spender === routerDestination`; unenforced
55
- * providers (li.fi/swapkit) are NOT bound. CowSwap's spender IS its `tx.to` (both are the fixed
56
- * GPv2VaultRelayer - see getSwapDestinationAddress.ts), so it binds the same way 1inch/kyber do. Like
57
- * its sibling this is a MONOTONIC gate: it only throws or no-ops, never changes the signed bytes.
63
+ * On the initiator the fixed-spender providers coincide by construction. LI.FI and SwapKit can
64
+ * instead set an explicit `approvalAddress`, which is why their spender checks are separate.
65
+ * Approval-bound providers (1inch/kyber/cowswap) MUST have `spender === routerDestination`.
66
+ * LI.FI approval spenders can vary by route and are instead checked by
67
+ * {@link assertLifiApprovalAddress}; SwapKit spenders use its corresponding reputation guard.
68
+ * CowSwap's spender IS its `tx.to` (both are the fixed GPv2VaultRelayer - see
69
+ * getSwapDestinationAddress.ts), so it binds the same way 1inch/kyber do. Like its sibling this is
70
+ * a MONOTONIC gate: it only throws or no-ops, never changes the signed bytes.
58
71
  */
59
72
  export declare function assertEnforcedSwapApprovalSpenderBound(provider: string, spender: string, routerDestination: string, chain: Chain): void;
60
73
  /**
@@ -63,9 +76,14 @@ export declare function assertEnforcedSwapApprovalSpenderBound(provider: string,
63
76
  */
64
77
  export declare function assertKnownAggregatorRouter(provider: EnforcedRouterProvider, address: string, chain: Chain): void;
65
78
  /**
66
- * Log-only (never throws) for LiFi/SwapKit, whose legitimate routes span many different
67
- * contracts. Structured so the data is queryable (provider + address) and a future allowlist
68
- * is easy to build if a stable pattern emerges from real usage.
79
+ * SwapKit v3 returns `targetAddress` independently from the ready-to-sign EVM
80
+ * transaction and defines it as the address the transaction must call or transfer to.
81
+ * Bind those fields before constructing a GeneralSwapQuote so a substituted `tx.to`
82
+ * cannot silently become the signed destination (or the approval fallback).
83
+ */
84
+ export declare function assertSwapKitDestinationMatchesTarget(address: string, targetAddress: string | undefined, chain: Chain): void;
85
+ /**
86
+ * Log-only (never throws) — retained only for legacy unattributed signing payloads.
69
87
  */
70
88
  export declare function logUnenforcedAggregatorDestination(provider: string, address: string): void;
71
89
  //# sourceMappingURL=knownAggregatorRouters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"knownAggregatorRouters.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/chain/swap/general/knownAggregatorRouters.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAwEnC,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAA;AAclE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,wCAAwC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAkB9G;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,KAAK,GACX,IAAI,CASN;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CA0BjH;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK1F"}
1
+ {"version":3,"file":"knownAggregatorRouters.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/chain/swap/general/knownAggregatorRouters.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAY,MAAM,aAAa,CAAA;AA+F7C,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAA;AAuB5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,wCAAwC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAuB9G;AAsCD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjH;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAMtF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,KAAK,GACX,IAAI,CASN;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CA4BjH;AAED;;;;;GAKG;AACH,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,KAAK,EAAE,KAAK,GACX,IAAI,CAYN;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK1F"}
@@ -1,5 +1,7 @@
1
1
  import { isOneOf } from '@vultisig/lib-utils/array/isOneOf';
2
2
  import { Chain } from '../../Chain.js';
3
+ import { scanAddressWithBlockaid } from '../../security/blockaid/address/index.js';
4
+ import { blockaidEvmChain } from '../../security/blockaid/evmChains.js';
3
5
  import { COW_VAULT_RELAYER_ADDRESS, cowSwapSupportedChains } from './cowswap/config.js';
4
6
  /**
5
7
  * Fund-safety allowlist for the general-purpose EVM swap aggregators (AGG-02, round-2
@@ -34,14 +36,15 @@ import { COW_VAULT_RELAYER_ADDRESS, cowSwapSupportedChains } from './cowswap/con
34
36
  * zkSync 1inch swap. Kyber showed no such variance on every chain that returned a live
35
37
  * response (see the per-chain notes below) — its allowlist stays flat.
36
38
  *
37
- * LiFi and SwapKit CANNOT be enforced the same way they route through many different
38
- * bridge/DEX contracts by design (diamond routing, multi-hop, chain-specific deployments),
39
- * so a hard allowlist would false-block legitimate routes. Those two are logged (never
40
- * thrown) via logUnenforcedAggregatorDestination so an anomaly is queryable, and so a future
41
- * allowlist has real usage data to build from if a pattern emerges. sdk#1457: because they are
42
- * the only genuinely unenforceable-by-address providers, they are the one residual gap
43
- * assertKnownAggregatorRouterOnSigningPath's provider-string closed-list still can't structurally
44
- * close - see that function's doc comment.
39
+ * LI.FI is enforced too (sdk#1458). Although its Diamond dispatches to many bridge/DEX contracts,
40
+ * the user-facing transaction enters through one officially published, chain-scoped Diamond.
41
+ * Most supported chains share the CREATE2 address below; HyperEVM, Robinhood, and zkSync do not.
42
+ * LI.FI's approval spender can vary by route, so the Diamond remains the deterministic fast path
43
+ * while any distinct spender requires an independent benign Blockaid verdict.
44
+ * SwapKit remains dynamic because its target can be a provider router or a per-swap deposit
45
+ * address. Its quote and co-signer paths therefore require an independent benign Blockaid
46
+ * reputation verdict via {@link assertSwapKitAddressReputation}; the response-local
47
+ * `targetAddress` equality check remains defense in depth only.
45
48
  */
46
49
  // 1inch Aggregation Router V5 (legacy). Same address as
47
50
  // chains/evm/contract/knownContracts.ts's display-only registry; kept separate here since
@@ -64,23 +67,49 @@ const ONE_INCH_V6_ROBINHOOD_ROUTER = '0x5a705de8982235a7fa45bb83dcacf03a211389c7
64
67
  // kyberSwapEnabledChains chain through aggregator-api.kyberswap.com's /routes
65
68
  // (Ethereum, BSC, Arbitrum, Optimism, Avalanche, Base, Polygon).
66
69
  const KYBER_STANDARD_ROUTER = '0x6131b5fae19ea4f9d964eac0408e4408b66337b5';
67
- const ENFORCED_ROUTER_PROVIDERS = new Set(['1inch', 'kyber', 'cowswap']);
68
- // sdk#1457: the small, closed set of provider values the codebase legitimately produces for a
69
- // route that is genuinely NOT address-allow-listable. `li.fi`/`swapkit` route through many
70
- // different bridge/DEX contracts by design (see the file doc comment). `''` is NOT an attacker
71
- // label - it is the documented fallback mapSwapPayload.ts (and getKeysignSwapPayload's own
70
+ // Official LI.FI Diamond deployment registry, read from
71
+ // github.com/lifinance/contracts/tree/main/deployments on 2026-08-08. Keep this
72
+ // deliberately chain-scoped: the exceptions are real deployments, not aliases.
73
+ const LIFI_DIAMOND_BY_CHAIN = {
74
+ [Chain.Arbitrum]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
75
+ [Chain.Avalanche]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
76
+ [Chain.Base]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
77
+ [Chain.Blast]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
78
+ [Chain.BSC]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
79
+ [Chain.CronosChain]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
80
+ [Chain.Ethereum]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
81
+ [Chain.Hyperliquid]: '0x0a0758d937d1059c356d4714e57f5df0239bce1a',
82
+ [Chain.Mantle]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
83
+ [Chain.Optimism]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
84
+ [Chain.Polygon]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
85
+ [Chain.Robinhood]: '0xb477751b76cf82d00a686a1232f5fcd772414af3',
86
+ [Chain.Sei]: '0x1231deb6f5749ef6ce6943a275a1d3e7486f4eae',
87
+ [Chain.Zksync]: '0x341e94069f53234fe6dabef707ad424830525715',
88
+ };
89
+ const ENFORCED_ROUTER_PROVIDERS = new Set([
90
+ '1inch',
91
+ 'kyber',
92
+ 'cowswap',
93
+ 'li.fi',
94
+ ]);
95
+ // These providers use the same fixed address for the user-facing router and approval spender.
96
+ // LI.FI is intentionally absent: its official API contract says approval spenders may vary by
97
+ // route or bridge, so distinct spenders use assertLifiApprovalAddress instead of hard equality.
98
+ const APPROVAL_BOUND_ROUTER_PROVIDERS = new Set(['1inch', 'kyber', 'cowswap']);
99
+ // sdk#1457/#1458: the small, closed set of recognized providers outside the fixed-router
100
+ // allowlists. SwapKit targets are dynamic and use Blockaid; `''` is NOT an attacker label - it is
101
+ // the documented fallback mapSwapPayload.ts (and getKeysignSwapPayload's own
72
102
  // callers) use for pre-provider-field mobile captures, proven by real golden fixtures
73
103
  // (mobileFixtures.golden.test.ts's arb.json/lifiswap.json) that still carry an unset provider.
74
104
  // This is a CLOSED list, not "everything not enforced": a `provider` string outside BOTH this
75
105
  // set and ENFORCED_ROUTER_PROVIDERS is unrecognized and rejected below, instead of silently logged.
76
- const RECOGNIZED_UNENFORCED_PROVIDERS = new Set(['li.fi', 'swapkit', '']);
106
+ const RECOGNIZED_DYNAMIC_OR_LEGACY_PROVIDERS = new Set(['swapkit', '']);
77
107
  /**
78
108
  * Signing-path re-assert (sdk#1358): the same allow-list check as {@link assertKnownAggregatorRouter},
79
109
  * but keyed off an arbitrary provider STRING (the value carried in KeysignSwapPayload.general.provider,
80
- * which is a plain `string`). Enforced providers (1inch/kyber/cowswap) fail closed; the small closed set
81
- * of genuinely unenforceable providers (li.fi/swapkit, plus the legacy `''` unattributed provider - see
82
- * RECOGNIZED_UNENFORCED_PROVIDERS) fall through to the same log-only path they take at quote construction;
83
- * anything else is rejected outright (sdk#1457, see below).
110
+ * which is a plain `string`). Fixed-router providers fail closed against their allowlists, SwapKit
111
+ * fails closed against an independent Blockaid reputation verdict, and only the legacy `''`
112
+ * unattributed provider remains log-only. Anything else is rejected outright (sdk#1457, see below).
84
113
  *
85
114
  * WHY THIS EXISTS SEPARATELY FROM quote construction: a compromised initiator (or server composing the
86
115
  * intent) can hand a co-signer a KeysignPayload whose swapPayload.quote.tx.to was NEVER run through the
@@ -97,18 +126,16 @@ const RECOGNIZED_UNENFORCED_PROVIDERS = new Set(['li.fi', 'swapkit', '']);
97
126
  * enforcement) is a real, closable gap - not just a theoretical one.
98
127
  *
99
128
  * sdk#1457 FIX: two structural improvements that need no proto change. (1) CowSwap is now enforced -
100
- * unlike li.fi/swapkit it settles through ONE fixed, deterministic contract (the GPv2VaultRelayer, see
129
+ * unlike swapkit it settles through ONE fixed, deterministic contract (the GPv2VaultRelayer, see
101
130
  * assertKnownAggregatorRouter), so its destination is exactly as allow-listable as 1inch/Kyber's; a
102
131
  * payload can no longer relabel itself 'cowswap' to dodge a router check the way it previously could. (2)
103
132
  * the log-only fallback is now a CLOSED list of the provider values the codebase legitimately produces
104
- * (li.fi/swapkit, plus the legacy `''` unattributed provider) - a `provider` string outside every known
133
+ * (at that time li.fi/swapkit, plus the legacy `''` unattributed provider) - a `provider` string outside every known
105
134
  * value (enforced or unenforced) is unrecognized and REJECTED, not silently passed through. Together
106
- * these shrink "relabel to escape enforcement" from "any string at all" down to exactly li.fi, swapkit,
107
- * and the legacy `''`, which remain unenforceable by address because they legitimately route through
108
- * many different contracts by design (see the file doc comment) - closing that residual gap needs the
109
- * provider identity to be a trusted wire discriminant instead of a free string, i.e. a proto oneof case
110
- * per provider (tracked in sdk#1457, not attempted here: a schema change on the shared commondata proto
111
- * is a cross-repo, cross-consumer change every native client (iOS/Android/Windows) also builds against).
135
+ * these originally shrank "relabel to escape enforcement" from "any string at all" to that closed set.
136
+ * sdk#1458 subsequently removes li.fi from the residual gap using its official Diamond deployments
137
+ * and applies an independent Blockaid reputation check to SwapKit destinations on both quote and
138
+ * co-signer paths. The legacy unattributed value remains the only log-only compatibility case.
112
139
  *
113
140
  * This guard remains MONOTONIC beyond that residual gap: it only ever THROWS (rejects) or no-ops - it
114
141
  * never makes anything signable that wasn't already.
@@ -120,16 +147,66 @@ export function assertKnownAggregatorRouterOnSigningPath(provider, address, chai
120
147
  assertKnownAggregatorRouter(provider, address, chain);
121
148
  return;
122
149
  }
123
- if (!RECOGNIZED_UNENFORCED_PROVIDERS.has(provider)) {
150
+ if (provider === 'swapkit') {
151
+ throw new Error('SwapKit destinations require the asynchronous independent reputation guard — refusing a synchronous log-only check.');
152
+ }
153
+ if (!RECOGNIZED_DYNAMIC_OR_LEGACY_PROVIDERS.has(provider)) {
124
154
  throw new Error(`Unrecognized swap provider "${provider}" on the co-signer signing path - refusing to sign a swap ` +
125
155
  'whose provider label does not match any known aggregator (enforced or unenforced).');
126
156
  }
127
- // Unenforced (li.fi/swapkit): log-only, and log the ACTUAL provider so the usage dataset the future
128
- // allow-list is built from isn't poisoned by a coerced label. Skip a genuinely empty address.
157
+ // Legacy unattributed payloads remain log-only for compatibility with historical mobile captures.
158
+ // Log the actual provider so the usage dataset isn't poisoned by a coerced label. Skip a genuinely empty address.
129
159
  if (address) {
130
160
  logUnenforcedAggregatorDestination(provider, address);
131
161
  }
132
162
  }
163
+ /**
164
+ * Independent reputation boundary shared by dynamic aggregator addresses. Only an explicit
165
+ * Benign Blockaid verdict is accepted; unsupported chains, scan failures, Warning, and Malicious
166
+ * verdicts all fail closed.
167
+ */
168
+ async function assertAggregatorAddressReputation(provider, address, chain, role = 'destination') {
169
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
170
+ throw new Error(`${provider} ${role} (${address}) is not a valid EVM address on ${chain}.`);
171
+ }
172
+ const blockaidChain = blockaidEvmChain[chain];
173
+ if (!blockaidChain) {
174
+ throw new Error(`${provider} ${role} reputation cannot be verified on unsupported Blockaid chain ${chain}.`);
175
+ }
176
+ let result;
177
+ try {
178
+ result = await scanAddressWithBlockaid(address, blockaidChain);
179
+ }
180
+ catch (error) {
181
+ const reason = error instanceof Error ? error.message : String(error);
182
+ throw new Error(`${provider} ${role} reputation check failed on ${chain}: ${reason}`);
183
+ }
184
+ if (result.resultType !== 'Benign') {
185
+ const features = result.features.length ? ` (${result.features.join(', ')})` : '';
186
+ throw new Error(`${provider} ${role} (${address}) received a ${result.resultType} Blockaid verdict on ${chain}${features} — refusing to sign or return the transaction.`);
187
+ }
188
+ }
189
+ /**
190
+ * Independent trust boundary for dynamic SwapKit EVM addresses. SwapKit's `tx.to`,
191
+ * `targetAddress`, and optional approval transaction all come from the same `/v3/swap`
192
+ * response, so equality between them cannot establish destination safety when that response
193
+ * is compromised.
194
+ */
195
+ export function assertSwapKitAddressReputation(address, chain, role = 'destination') {
196
+ return assertAggregatorAddressReputation('SwapKit', address, chain, role);
197
+ }
198
+ /**
199
+ * LI.FI documents `estimate.approvalAddress` as route-dependent and explicitly warns consumers
200
+ * not to hardcode it. The official Diamond is safe without a network dependency; a distinct
201
+ * spender is accepted only after an independent benign Blockaid verdict. This preserves valid
202
+ * inner-executor routes without trusting the quote response to authorize its own spender.
203
+ */
204
+ export function assertLifiApprovalAddress(address, chain) {
205
+ if (address.toLowerCase() === LIFI_DIAMOND_BY_CHAIN[chain]) {
206
+ return Promise.resolve();
207
+ }
208
+ return assertAggregatorAddressReputation('LI.FI', address, chain, 'approval spender');
209
+ }
133
210
  /**
134
211
  * Signing-path approval-spender bind (sdk#1358 review follow-up, requested by neavra). The
135
212
  * follow-on to {@link assertKnownAggregatorRouterOnSigningPath}: that guard validates the swap-leg
@@ -139,15 +216,17 @@ export function assertKnownAggregatorRouterOnSigningPath(provider, address, chai
139
216
  * genuine 1inch/kyber `tx.to` yet still carry an approve granting an ATTACKER an allowance over the
140
217
  * user's token (a classic approval-drain the co-signer would otherwise sign blind).
141
218
  *
142
- * On the initiator these coincide by construction (build.ts sets the approve spender to
143
- * `getSwapDestinationAddress` === `tx.to`), so this only ever fires on a hand-built/tampered payload.
144
- * Enforced providers (1inch/kyber/cowswap) MUST have `spender === routerDestination`; unenforced
145
- * providers (li.fi/swapkit) are NOT bound. CowSwap's spender IS its `tx.to` (both are the fixed
146
- * GPv2VaultRelayer - see getSwapDestinationAddress.ts), so it binds the same way 1inch/kyber do. Like
147
- * its sibling this is a MONOTONIC gate: it only throws or no-ops, never changes the signed bytes.
219
+ * On the initiator the fixed-spender providers coincide by construction. LI.FI and SwapKit can
220
+ * instead set an explicit `approvalAddress`, which is why their spender checks are separate.
221
+ * Approval-bound providers (1inch/kyber/cowswap) MUST have `spender === routerDestination`.
222
+ * LI.FI approval spenders can vary by route and are instead checked by
223
+ * {@link assertLifiApprovalAddress}; SwapKit spenders use its corresponding reputation guard.
224
+ * CowSwap's spender IS its `tx.to` (both are the fixed GPv2VaultRelayer - see
225
+ * getSwapDestinationAddress.ts), so it binds the same way 1inch/kyber do. Like its sibling this is
226
+ * a MONOTONIC gate: it only throws or no-ops, never changes the signed bytes.
148
227
  */
149
228
  export function assertEnforcedSwapApprovalSpenderBound(provider, spender, routerDestination, chain) {
150
- if (!ENFORCED_ROUTER_PROVIDERS.has(provider)) {
229
+ if (!APPROVAL_BOUND_ROUTER_PROVIDERS.has(provider)) {
151
230
  return;
152
231
  }
153
232
  if (spender.toLowerCase() !== routerDestination.toLowerCase()) {
@@ -162,29 +241,43 @@ export function assertKnownAggregatorRouter(provider, address, chain) {
162
241
  const normalized = address.toLowerCase();
163
242
  const isKnown = provider === 'kyber'
164
243
  ? normalized === KYBER_STANDARD_ROUTER
165
- : provider === 'cowswap'
166
- ? // CHAIN-SCOPED, same reason the 1inch arm is: the relayer is a deterministic address, so it
167
- // resolves on EVERY EVM chain, but CoW has only deployed the GPv2 stack on
168
- // cowSwapSupportedChains (findSwapQuote gates quotes to exactly those). Accepting it
169
- // chain-agnostically would let a tampered payload relabelled 'cowswap' on e.g. CronosChain /
170
- // Zksync / Blast where eth_getCode at this address is literally `0x`, verified 2026-07-21 —
171
- // pass BOTH this guard and assertEnforcedSwapApprovalSpenderBound, so the co-signer would
172
- // sign an ERC-20 approve to a codeless address anyone can later claim via the deterministic
173
- // deployment proxy. Fail closed off the supported set.
174
- isOneOf(chain, cowSwapSupportedChains) && normalized === COW_VAULT_RELAYER_ADDRESS.toLowerCase()
175
- : chain === Chain.Zksync
176
- ? normalized === ONE_INCH_V6_ZKSYNC_ROUTER
177
- : chain === Chain.Robinhood
178
- ? normalized === ONE_INCH_V6_ROBINHOOD_ROUTER
179
- : normalized === ONE_INCH_V5_ROUTER || normalized === ONE_INCH_V6_STANDARD_ROUTER;
244
+ : provider === 'li.fi'
245
+ ? normalized === LIFI_DIAMOND_BY_CHAIN[chain]
246
+ : provider === 'cowswap'
247
+ ? // CHAIN-SCOPED, same reason the 1inch arm is: the relayer is a deterministic address, so it
248
+ // resolves on EVERY EVM chain, but CoW has only deployed the GPv2 stack on
249
+ // cowSwapSupportedChains (findSwapQuote gates quotes to exactly those). Accepting it
250
+ // chain-agnostically would let a tampered payload relabelled 'cowswap' on e.g. CronosChain /
251
+ // Zksync / Blast where eth_getCode at this address is literally `0x`, verified 2026-07-21
252
+ // pass BOTH this guard and assertEnforcedSwapApprovalSpenderBound, so the co-signer would
253
+ // sign an ERC-20 approve to a codeless address anyone can later claim via the deterministic
254
+ // deployment proxy. Fail closed off the supported set.
255
+ isOneOf(chain, cowSwapSupportedChains) && normalized === COW_VAULT_RELAYER_ADDRESS.toLowerCase()
256
+ : chain === Chain.Zksync
257
+ ? normalized === ONE_INCH_V6_ZKSYNC_ROUTER
258
+ : chain === Chain.Robinhood
259
+ ? normalized === ONE_INCH_V6_ROBINHOOD_ROUTER
260
+ : normalized === ONE_INCH_V5_ROUTER || normalized === ONE_INCH_V6_STANDARD_ROUTER;
180
261
  if (!isKnown) {
181
262
  throw new Error(`${provider} swap quote returned an unrecognized router address (${address}) on ${chain} — refusing to build a signable transaction against it.`);
182
263
  }
183
264
  }
184
265
  /**
185
- * Log-only (never throws) for LiFi/SwapKit, whose legitimate routes span many different
186
- * contracts. Structured so the data is queryable (provider + address) and a future allowlist
187
- * is easy to build if a stable pattern emerges from real usage.
266
+ * SwapKit v3 returns `targetAddress` independently from the ready-to-sign EVM
267
+ * transaction and defines it as the address the transaction must call or transfer to.
268
+ * Bind those fields before constructing a GeneralSwapQuote so a substituted `tx.to`
269
+ * cannot silently become the signed destination (or the approval fallback).
270
+ */
271
+ export function assertSwapKitDestinationMatchesTarget(address, targetAddress, chain) {
272
+ if (!targetAddress || !/^0x[0-9a-fA-F]{40}$/.test(targetAddress)) {
273
+ throw new Error(`SwapKit swap response did not include a valid targetAddress on ${chain} — refusing to trust tx.to.`);
274
+ }
275
+ if (address.toLowerCase() !== targetAddress.toLowerCase()) {
276
+ throw new Error(`SwapKit swap transaction destination (${address}) does not match the screened targetAddress (${targetAddress}) on ${chain} — refusing to build a signable transaction.`);
277
+ }
278
+ }
279
+ /**
280
+ * Log-only (never throws) — retained only for legacy unattributed signing payloads.
188
281
  */
189
282
  export function logUnenforcedAggregatorDestination(provider, address) {
190
283
  console.info('[swap-router-telemetry] general-swap destination (not enforced, logged for future analysis):', {
@@ -1 +1 @@
1
- {"version":3,"file":"knownAggregatorRouters.js","sourceRoot":"","sources":["../../../../../../packages/core/chain/swap/general/knownAggregatorRouters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAA;AAE3D,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACnC,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,wDAAwD;AACxD,0FAA0F;AAC1F,4FAA4F;AAC5F,wFAAwF;AACxF,qFAAqF;AACrF,oDAAoD;AACpD,MAAM,kBAAkB,GAAG,4CAA4C,CAAA;AAEvE,sFAAsF;AACtF,yEAAyE;AACzE,MAAM,2BAA2B,GAAG,4CAA4C,CAAA;AAEhF,sFAAsF;AACtF,0EAA0E;AAC1E,MAAM,yBAAyB,GAAG,4CAA4C,CAAA;AAE9E,yFAAyF;AACzF,qFAAqF;AACrF,uFAAuF;AACvF,MAAM,4BAA4B,GAAG,4CAA4C,CAAA;AAEjF,sFAAsF;AACtF,8EAA8E;AAC9E,iEAAiE;AACjE,MAAM,qBAAqB,GAAG,4CAA4C,CAAA;AAI1E,MAAM,yBAAyB,GAAwB,IAAI,GAAG,CAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAA;AAErH,8FAA8F;AAC9F,2FAA2F;AAC3F,+FAA+F;AAC/F,2FAA2F;AAC3F,sFAAsF;AACtF,+FAA+F;AAC/F,8FAA8F;AAC9F,oGAAoG;AACpG,MAAM,+BAA+B,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAA;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,UAAU,wCAAwC,CAAC,QAAgB,EAAE,OAAe,EAAE,KAAY;IACtG,IAAI,yBAAyB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,kGAAkG;QAClG,kGAAkG;QAClG,2BAA2B,CAAC,QAAkC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;QAC/E,OAAM;IACR,CAAC;IACD,IAAI,CAAC,+BAA+B,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,+BAA+B,QAAQ,4DAA4D;YACjG,oFAAoF,CACvF,CAAA;IACH,CAAC;IACD,oGAAoG;IACpG,8FAA8F;IAC9F,IAAI,OAAO,EAAE,CAAC;QACZ,kCAAkC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IACvD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sCAAsC,CACpD,QAAgB,EAChB,OAAe,EACf,iBAAyB,EACzB,KAAY;IAEZ,IAAI,CAAC,yBAAyB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7C,OAAM;IACR,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,iBAAiB,CAAC,WAAW,EAAE,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CACb,GAAG,QAAQ,2BAA2B,OAAO,8CAA8C,iBAAiB,QAAQ,KAAK,wDAAwD,CAClL,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAAgC,EAAE,OAAe,EAAE,KAAY;IACzG,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAA;IACxC,MAAM,OAAO,GACX,QAAQ,KAAK,OAAO;QAClB,CAAC,CAAC,UAAU,KAAK,qBAAqB;QACtC,CAAC,CAAC,QAAQ,KAAK,SAAS;YACtB,CAAC,CAAC,4FAA4F;gBAC5F,2EAA2E;gBAC3E,qFAAqF;gBACrF,6FAA6F;gBAC7F,8FAA8F;gBAC9F,0FAA0F;gBAC1F,4FAA4F;gBAC5F,uDAAuD;gBACvD,OAAO,CAAC,KAAK,EAAE,sBAAsB,CAAC,IAAI,UAAU,KAAK,yBAAyB,CAAC,WAAW,EAAE;YAClG,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,MAAM;gBACtB,CAAC,CAAC,UAAU,KAAK,yBAAyB;gBAC1C,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,SAAS;oBACzB,CAAC,CAAC,UAAU,KAAK,4BAA4B;oBAC7C,CAAC,CAAC,UAAU,KAAK,kBAAkB,IAAI,UAAU,KAAK,2BAA2B,CAAA;IAE3F,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,GAAG,QAAQ,wDAAwD,OAAO,QAAQ,KAAK,yDAAyD,CACjJ,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAAC,QAAgB,EAAE,OAAe;IAClF,OAAO,CAAC,IAAI,CAAC,8FAA8F,EAAE;QAC3G,QAAQ;QACR,OAAO;KACR,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"knownAggregatorRouters.js","sourceRoot":"","sources":["../../../../../../packages/core/chain/swap/general/knownAggregatorRouters.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"getLifiSwapQuote.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/chain/swap/general/lifi/api/getLifiSwapQuote.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,mBAAmB,EAGpB,MAAM,+CAA+C,CAAA;AACtD,OAAO,EAAmB,oBAAoB,EAAE,MAAM,8DAA8D,CAAA;AAKpH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAEzE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAKzD,KAAK,KAAK,GAAG,MAAM,CAAC,iBAAiB,EAAE,cAAc,CAAC,oBAAoB,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG;IACnG,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;4EACwE;IACxE,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;qDACiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,qFAAqF;AACrF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AA2C7D,eAAO,MAAM,gBAAgB,GAAU,wFAMpC,KAAK,KAAG,OAAO,CAAC,gBAAgB,CA0MlC,CAAA"}
1
+ {"version":3,"file":"getLifiSwapQuote.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/chain/swap/general/lifi/api/getLifiSwapQuote.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,mBAAmB,EAGpB,MAAM,+CAA+C,CAAA;AACtD,OAAO,EAAmB,oBAAoB,EAAE,MAAM,8DAA8D,CAAA;AAIpH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAEzE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAMzD,KAAK,KAAK,GAAG,MAAM,CAAC,iBAAiB,EAAE,cAAc,CAAC,oBAAoB,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG;IACnG,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;4EACwE;IACxE,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;qDACiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,qFAAqF;AACrF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAgB7D,eAAO,MAAM,gBAAgB,GAAU,wFAMpC,KAAK,KAAG,OAAO,CAAC,gBAAgB,CAoMlC,CAAA"}
@@ -7,10 +7,10 @@ import { lifiSwapChainId } from '@vultisig/core-chain/swap/general/lifi/LifiSwap
7
7
  import { shouldBePresent } from '@vultisig/lib-utils/assert/shouldBePresent';
8
8
  import { match } from '@vultisig/lib-utils/match';
9
9
  import { memoize } from '@vultisig/lib-utils/memoize';
10
- import { mirrorRecord } from '@vultisig/lib-utils/record/mirrorRecord';
11
- import { logUnenforcedAggregatorDestination } from '../../knownAggregatorRouters.js';
10
+ import { assertKnownAggregatorRouter, assertLifiApprovalAddress } from '../../knownAggregatorRouters.js';
12
11
  import { injectSolanaAtaIfMissing } from './injectSolanaAtaIfMissing.js';
13
12
  import { MAX_COMBINED_COST_BPS, resolveLifiSlippage } from './lifiSlippage.js';
13
+ import { resolveSwapFeeChain } from './lifiSwapFeeChain.js';
14
14
  /** @internal re-exported for unit tests only — source of truth is ./lifiSlippage. */
15
15
  export { isStablePair, STABLE_TICKERS } from './lifiSlippage.js';
16
16
  /** Lazy bootstrap for callers that never invoked the public `setupLifi`
@@ -25,32 +25,6 @@ const ensureLifiConfigured = memoize(() => {
25
25
  // ./lifiSlippage — the shared source of truth for the two tiers + combined-cost ceiling, so the RN
26
26
  // override resolves it identically. See that module for the full rationale (MPC ceremony drift, the
27
27
  // stable/volatile split, and the cross-chain bridge-slippage caveat).
28
- // Resolve a LiFi fee-token `chainId` back to a Vultisig `LifiSwapEnabledChain`.
29
- //
30
- // `mirrorRecord(lifiSwapChainId)` is a closed, build-time map of LI.FI source
31
- // chains (EVM chains Vultisig exposes + Solana). LI.FI's `feeCosts[].token`
32
- // is *not* guaranteed by the API contract to live on the source chain — for
33
- // cross-chain routes (e.g. EVM → Cosmos via Stargate, EVM → BTC via THORChain
34
- // relay) the fee can be denominated on an intermediate chain whose ID is not
35
- // a key in this map. `mirrorRecord(...)[unknownChainId]` then silently yields
36
- // `undefined`, producing `affiliateFee.chain = undefined`, which serializes
37
- // as an absent `swap_fee_chain` field alongside a non-empty `swap_fee` —
38
- // exactly the ambiguous state this PR set out to eliminate.
39
- //
40
- // Fall back to `transfer.from.chain` because LiFi's fixed fee is collected
41
- // from the user's source-chain wallet regardless of which bridge leg
42
- // denominates it internally, and the source chain is always a
43
- // `LifiSwapEnabledChain` (guaranteed by the `Input` type). Warn so any
44
- // future LiFi behavioural drift is visible in ops telemetry rather than
45
- // silently misattributed. (NeOMakinG #540 review blocking #1.)
46
- const resolveSwapFeeChain = (chainId, fallback) => {
47
- const resolved = mirrorRecord(lifiSwapChainId)[chainId];
48
- if (resolved === undefined) {
49
- console.warn(`[getLifiSwapQuote] fee token chainId ${chainId} not in lifiSwapChainId; falling back to ${fallback}`);
50
- return fallback;
51
- }
52
- return resolved;
53
- };
54
28
  export const getLifiSwapQuote = async ({ amount, affiliateBps, lifiAffiliateConfig, slippage: slippageOverride, ...transfer }) => {
55
29
  ensureLifiConfigured();
56
30
  const [fromChain, toChain] = [transfer.from, transfer.to].map(({ chain }) => lifiSwapChainId[chain]);
@@ -140,6 +114,13 @@ export const getLifiSwapQuote = async ({ amount, affiliateBps, lifiAffiliateConf
140
114
  },
141
115
  };
142
116
  }
117
+ const approvalAddr = estimate.approvalAddress;
118
+ if (chainKind === 'evm') {
119
+ assertKnownAggregatorRouter('li.fi', shouldBePresent(to), transfer.from.chain);
120
+ if (approvalAddr && approvalAddr !== '0x0000000000000000000000000000000000000000') {
121
+ await assertLifiApprovalAddress(approvalAddr, transfer.from.chain);
122
+ }
123
+ }
143
124
  return {
144
125
  dstAmount: estimate.toAmount,
145
126
  provider: 'li.fi',
@@ -181,27 +162,13 @@ export const getLifiSwapQuote = async ({ amount, affiliateBps, lifiAffiliateConf
181
162
  const swapFeeAssetId = swapFee &&
182
163
  ([fromToken, toToken].find(token => token.toLowerCase() === swapFeeAddress) ||
183
164
  chainFeeCoin[transfer.from.chain].id);
184
- // LI.FI `estimate.approvalAddress` is the address that will call
185
- // `transferFrom` on the input ERC-20. It can differ from `to` (the
186
- // Diamond / router) when an inner executor (e.g. 1inch
187
- // AggregationExecutor) pulls the token directly. The field is always
188
- // present in the LiFi API response (`Estimate.approvalAddress: string`)
189
- // but may be the zero address or equal to `to` for native-token routes.
190
- // Pass it through so mcp-ts (and other consumers) can approve the
191
- // correct spender instead of the Diamond.
192
- //
193
- // On-chain proof: tx 0xa3aadf17 (Ethereum, block 25415989) reverted
194
- // with "ERC20: transfer amount exceeds allowance". Vault had 9.41 USDC
195
- // approved to Diamond (0x9025B8ff…, = `to`) — sufficient. Inner 1inch
196
- // executor (0x7f51c134…, = `approvalAddress`) had zero allowance — the
197
- // actual transferFrom caller → revert.
198
- const approvalAddr = estimate.approvalAddress;
165
+ // LI.FI `estimate.approvalAddress` is the spender that will pull the
166
+ // user's input ERC-20. LI.FI documents it as route-dependent, so it can
167
+ // differ from the Diamond destination. Treat it as independently
168
+ // attacker-controlled: the official Diamond is the deterministic fast
169
+ // path, while a distinct spender must receive an independent benign
170
+ // Blockaid verdict before it is exposed to consumers.
199
171
  const evmTo = shouldBePresent(to);
200
- // AGG-02: LiFi routes through many different bridge/DEX contracts by design
201
- // (diamond routing, multi-hop, chain-specific deployments) — a hard allowlist
202
- // would false-block legitimate routes, so log (never throw). See
203
- // knownAggregatorRouters.ts.
204
- logUnenforcedAggregatorDestination('li.fi', evmTo);
205
172
  return {
206
173
  evm: {
207
174
  from: shouldBePresent(from),