@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.
- package/CHANGELOG.md +21 -0
- package/dist/chains/sui/listAllCoins.d.ts.map +1 -1
- package/dist/chains/sui/listAllCoins.js +8 -0
- package/dist/chains/sui/listAllCoins.js.map +1 -1
- package/dist/swap/general/knownAggregatorRouters.d.ts +40 -22
- package/dist/swap/general/knownAggregatorRouters.d.ts.map +1 -1
- package/dist/swap/general/knownAggregatorRouters.js +147 -54
- package/dist/swap/general/knownAggregatorRouters.js.map +1 -1
- package/dist/swap/general/lifi/api/getLifiSwapQuote.d.ts.map +1 -1
- package/dist/swap/general/lifi/api/getLifiSwapQuote.js +15 -48
- package/dist/swap/general/lifi/api/getLifiSwapQuote.js.map +1 -1
- package/dist/swap/general/lifi/api/lifiSwapFeeChain.d.ts +4 -0
- package/dist/swap/general/lifi/api/lifiSwapFeeChain.d.ts.map +1 -0
- package/dist/swap/general/lifi/api/lifiSwapFeeChain.js +19 -0
- package/dist/swap/general/lifi/api/lifiSwapFeeChain.js.map +1 -0
- package/dist/swap/general/swapkit/api/getSwapKitQuote.d.ts.map +1 -1
- package/dist/swap/general/swapkit/api/getSwapKitQuote.js +24 -44
- package/dist/swap/general/swapkit/api/getSwapKitQuote.js.map +1 -1
- package/dist/swap/quote/SwapQuote.d.ts +12 -0
- package/dist/swap/quote/SwapQuote.d.ts.map +1 -1
- package/dist/swap/quote/findSwapQuote.d.ts +9 -2
- package/dist/swap/quote/findSwapQuote.d.ts.map +1 -1
- package/dist/swap/quote/findSwapQuote.js +32 -2
- package/dist/swap/quote/findSwapQuote.js.map +1 -1
- package/dist/swap/quote/getSwapQuoteSafetyFingerprint.d.ts +20 -0
- package/dist/swap/quote/getSwapQuoteSafetyFingerprint.d.ts.map +1 -0
- package/dist/swap/quote/getSwapQuoteSafetyFingerprint.js +67 -0
- package/dist/swap/quote/getSwapQuoteSafetyFingerprint.js.map +1 -0
- 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,
|
|
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`).
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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
|
-
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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`).
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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
|
|
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
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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 (
|
|
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
|
-
//
|
|
128
|
-
//
|
|
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
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
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 (!
|
|
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 === '
|
|
166
|
-
?
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
: chain === Chain.
|
|
178
|
-
? normalized ===
|
|
179
|
-
:
|
|
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
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
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,
|
|
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;
|
|
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 {
|
|
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
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
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),
|