@rabby-wallet/rabby-swap 0.0.85 → 0.0.86-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { CHAINS_ENUM, CHAINS } from "@debank/common";
2
2
  import { Interface } from "@ethersproject/abi";
3
- import { generateGetQuote, isSameAddress } from "../utils";
3
+ import { generateGetQuote, isSameAddress, resolveToTokenReceiver } from "../utils";
4
4
  import { KyberswapABI } from "../abi";
5
5
  import { DEX_ENUM } from "../consts";
6
6
  const NATIVE_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
@@ -43,10 +43,10 @@ export const decodeCalldata = (tx) => {
43
43
  const result = contractInterface.parseTransaction({ data: tx.data });
44
44
  let desc;
45
45
  if (result.name === "swapSimpleMode") {
46
- const [execution] = result.args;
47
- desc = execution.desc;
46
+ const [, swapDesc] = result.args;
47
+ desc = swapDesc;
48
48
  }
49
- if (result.name === "swap") {
49
+ else if (result.name === "swap") {
50
50
  const [execution] = result.args;
51
51
  desc = execution.desc;
52
52
  }
@@ -63,6 +63,6 @@ export const decodeCalldata = (tx) => {
63
63
  ? chain.nativeTokenAddress
64
64
  : dstToken,
65
65
  minReceiveToTokenAmount: minReturnAmount.toString(),
66
- toTokenReceiver: tx.from,
66
+ toTokenReceiver: resolveToTokenReceiver(dstReceiver, tx.from),
67
67
  };
68
68
  };
@@ -1,6 +1,6 @@
1
1
  import { Interface } from "@ethersproject/abi";
2
2
  import { CHAINS_ENUM, CHAINS } from "@debank/common";
3
- import { generateGetQuote, isSameAddress } from "../utils";
3
+ import { generateGetQuote, isSameAddress, resolveToTokenReceiver } from "../utils";
4
4
  import { OpenOceanABI } from "../abi";
5
5
  import { DEX_ENUM } from "../consts";
6
6
  const NATIVE_TOKENS = {
@@ -111,6 +111,6 @@ export const decodeCalldata = (tx) => {
111
111
  ? chain.nativeTokenAddress
112
112
  : dstToken,
113
113
  minReceiveToTokenAmount: minReturnAmount.toString(),
114
- toTokenReceiver: tx.from,
114
+ toTokenReceiver: resolveToTokenReceiver(dstReceiver, tx.from),
115
115
  };
116
116
  };
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ import { getQuote } from "./quote";
3
3
  import { DEX_ENUM } from "./consts";
4
4
  import { UNI_NATIVE_TO_ADDRESSES } from "./dexs/uni";
5
5
  export * from "./tokenPools";
6
+ export * from "./verify";
6
7
  export { WrapTokenAddressMap, DEX_ENUM, DEX_SUPPORT_CHAINS, getQuote, DEX_ROUTER_WHITELIST, DEX_SPENDER_WHITELIST, UNI_NATIVE_TO_ADDRESSES, };
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ import { getQuote } from "./quote";
3
3
  import { DEX_ENUM } from "./consts";
4
4
  import { UNI_NATIVE_TO_ADDRESSES } from "./dexs/uni";
5
5
  export * from "./tokenPools";
6
+ export * from "./verify";
6
7
  export { WrapTokenAddressMap, DEX_ENUM, DEX_SUPPORT_CHAINS, getQuote, DEX_ROUTER_WHITELIST, DEX_SPENDER_WHITELIST, UNI_NATIVE_TO_ADDRESSES, };
package/dist/utils.d.ts CHANGED
@@ -3,6 +3,8 @@ import { CHAINS_ENUM } from "@debank/common";
3
3
  import { QuoteParams, QuoteResult } from "./quote";
4
4
  import { DEX_ENUM } from "./consts";
5
5
  export declare const isSameAddress: (addr1: string, addr2: string) => boolean;
6
+ export declare const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
7
+ export declare const resolveToTokenReceiver: (encodedReceiver: string, txFrom: string) => string;
6
8
  export declare const generateGetQuote: ({ SUPPORT_CHAINS, id, dex, }: {
7
9
  SUPPORT_CHAINS: CHAINS_ENUM[];
8
10
  id: "matcha" | "matcha_v2" | "openocean" | "1inch_v6" | "paraswap" | "kyberswap" | "odos" | "paraswap_v6" | "magpie" | "sushi" | "rabby" | "uni";
package/dist/utils.js CHANGED
@@ -15,6 +15,8 @@ export const isSameAddress = (addr1, addr2) => {
15
15
  return false;
16
16
  return addr1.toLowerCase() === addr2.toLowerCase();
17
17
  };
18
+ export const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
19
+ export const resolveToTokenReceiver = (encodedReceiver, txFrom) => (isSameAddress(encodedReceiver, NULL_ADDRESS) ? txFrom : encodedReceiver);
18
20
  export const generateGetQuote = ({ SUPPORT_CHAINS, id, dex, }) => (options, api) => __awaiter(void 0, void 0, void 0, function* () {
19
21
  if (!options.chainServerId || !options.nativeTokenAddress) {
20
22
  throw new Error(`lack chainServerId or nativeTokenAddress is not support}`);
@@ -0,0 +1,25 @@
1
+ import { DEX_ENUM } from "../consts";
2
+ import { DecodeCalldataResult, QuoteResult, TxWithChainId } from "../quote";
3
+ /** 解码出的 minReceive 相对「报价 × (1-滑点)」允许的相对偏差。两端现网均为 5%。 */
4
+ export declare const CALLDATA_MIN_RECEIVE_TOLERANCE = 0.05;
5
+ /**
6
+ * 尝试解码。wrap、无 dex、无 tx、解码抛错或解不出时返回 null。
7
+ */
8
+ export declare const decodeSwapCalldata: (dexId: DEX_ENUM | null, tx?: TxWithChainId) => DecodeCalldataResult | null;
9
+ /** 纯比对:token、支付数量、最低到手(相对滑点带)。 */
10
+ export declare const matchDecodedCalldata: (decoded: DecodeCalldataResult, quote: QuoteResult, slippageRatio: string | number) => boolean;
11
+ export type VerifyCalldataParams = {
12
+ data: QuoteResult | null;
13
+ dexId: DEX_ENUM | null;
14
+ /** 已换算成小数的滑点,如 1% → 0.01。 */
15
+ slippage: string | number;
16
+ tx?: TxWithChainId;
17
+ /** 传入则不再解码。 */
18
+ decoded?: DecodeCalldataResult | null;
19
+ };
20
+ /** 解不出、无滑点或无报价时跳过比对(现网视为通过)。 */
21
+ export declare const shouldSkipCalldataMatch: (slippage: string | number, decoded: DecodeCalldataResult | null, data: QuoteResult | null, tx?: TxWithChainId) => boolean;
22
+ /**
23
+ * 解码并与报价比对。解码成功后不再依赖应用链目录。
24
+ */
25
+ export declare const verifyCalldata: (p: VerifyCalldataParams) => boolean;
@@ -0,0 +1,44 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { DEX_ENUM } from "../consts";
3
+ import { decodeCalldata, } from "../quote";
4
+ import { isSameAddress } from "../utils";
5
+ /** 解码出的 minReceive 相对「报价 × (1-滑点)」允许的相对偏差。两端现网均为 5%。 */
6
+ export const CALLDATA_MIN_RECEIVE_TOLERANCE = 0.05;
7
+ /**
8
+ * 尝试解码。wrap、无 dex、无 tx、解码抛错或解不出时返回 null。
9
+ */
10
+ export const decodeSwapCalldata = (dexId, tx) => {
11
+ if (!dexId || dexId === DEX_ENUM.WRAPTOKEN || !tx) {
12
+ return null;
13
+ }
14
+ try {
15
+ return decodeCalldata(dexId, tx);
16
+ }
17
+ catch (_a) {
18
+ return null;
19
+ }
20
+ };
21
+ /** 纯比对:token、支付数量、最低到手(相对滑点带)。 */
22
+ export const matchDecodedCalldata = (decoded, quote, slippageRatio) => {
23
+ const estimateMinReceive = new BigNumber(quote.toTokenAmount).times(new BigNumber(1).minus(slippageRatio));
24
+ return (isSameAddress(decoded.fromToken, quote.fromToken) &&
25
+ decoded.fromTokenAmount === quote.fromTokenAmount &&
26
+ isSameAddress(decoded.toToken, quote.toToken) &&
27
+ new BigNumber(decoded.minReceiveToTokenAmount)
28
+ .minus(estimateMinReceive)
29
+ .div(estimateMinReceive)
30
+ .abs()
31
+ .lte(CALLDATA_MIN_RECEIVE_TOLERANCE));
32
+ };
33
+ /** 解不出、无滑点或无报价时跳过比对(现网视为通过)。 */
34
+ export const shouldSkipCalldataMatch = (slippage, decoded, data, tx) => !slippage || !decoded || !data || !tx;
35
+ /**
36
+ * 解码并与报价比对。解码成功后不再依赖应用链目录。
37
+ */
38
+ export const verifyCalldata = (p) => {
39
+ const decoded = p.decoded !== undefined ? p.decoded : decodeSwapCalldata(p.dexId, p.tx);
40
+ if (shouldSkipCalldataMatch(p.slippage, decoded, p.data, p.tx)) {
41
+ return true;
42
+ }
43
+ return matchDecodedCalldata(decoded, p.data, p.slippage);
44
+ };
@@ -0,0 +1,20 @@
1
+ import { CHAINS_ENUM } from "@debank/common";
2
+ import { DEX_ENUM } from "../consts";
3
+ /** 取该 DEX 在此链上的 router。Uni 用 native 支付时走独立入口地址。 */
4
+ export declare const getRouter: (dexId: DEX_ENUM, chain: CHAINS_ENUM, payTokenId: string, nativeTokenAddress: string) => string | undefined;
5
+ /** wrap 无 spender;其余读白名单。 */
6
+ export declare const getSpender: (dexId: DEX_ENUM, chain: CHAINS_ENUM) => string | undefined;
7
+ export type VerifyRouterAndSpenderParams = {
8
+ chain: CHAINS_ENUM;
9
+ dexId: DEX_ENUM;
10
+ router?: string;
11
+ spender?: string;
12
+ payTokenId?: string;
13
+ receiveTokenId?: string;
14
+ nativeTokenAddress: string;
15
+ };
16
+ /**
17
+ * 校验 tx.to / approve spender 是否在白名单。
18
+ * wrap、native 支付或缺少 spender 时跳过 spender 校验。
19
+ */
20
+ export declare const verifyRouterAndSpender: (p: VerifyRouterAndSpenderParams) => [boolean, boolean];
@@ -0,0 +1,48 @@
1
+ import { DEX_ENUM } from "../consts";
2
+ import { UNI_NATIVE_TO_ADDRESSES } from "../dexs/uni";
3
+ import { DEX_ROUTER_WHITELIST, DEX_SPENDER_WHITELIST } from "../list";
4
+ import { isSameAddress } from "../utils";
5
+ import { isSwapWrapToken } from "./wrap";
6
+ const isNativeToken = (tokenId, nativeTokenAddress) => isSameAddress(tokenId, nativeTokenAddress);
7
+ /** 取该 DEX 在此链上的 router。Uni 用 native 支付时走独立入口地址。 */
8
+ export const getRouter = (dexId, chain, payTokenId, nativeTokenAddress) => {
9
+ if (dexId === DEX_ENUM.UNI && isNativeToken(payTokenId, nativeTokenAddress)) {
10
+ return UNI_NATIVE_TO_ADDRESSES[chain];
11
+ }
12
+ const list = DEX_ROUTER_WHITELIST[dexId];
13
+ if (!list)
14
+ return undefined;
15
+ return list[chain];
16
+ };
17
+ /** wrap 无 spender;其余读白名单。 */
18
+ export const getSpender = (dexId, chain) => {
19
+ if (dexId === DEX_ENUM.WRAPTOKEN) {
20
+ return "";
21
+ }
22
+ const list = DEX_SPENDER_WHITELIST[dexId];
23
+ if (!list)
24
+ return undefined;
25
+ return list[chain];
26
+ };
27
+ /**
28
+ * 校验 tx.to / approve spender 是否在白名单。
29
+ * wrap、native 支付或缺少 spender 时跳过 spender 校验。
30
+ */
31
+ export const verifyRouterAndSpender = (p) => {
32
+ const { chain, dexId, router, spender, payTokenId, receiveTokenId, nativeTokenAddress, } = p;
33
+ if (dexId === DEX_ENUM.WRAPTOKEN) {
34
+ return [true, true];
35
+ }
36
+ if (!dexId || !router || !payTokenId || !receiveTokenId) {
37
+ return [true, true];
38
+ }
39
+ const routerWhitelist = getRouter(dexId, chain, payTokenId, nativeTokenAddress);
40
+ const spenderWhitelist = getSpender(dexId, chain);
41
+ const skipSpender = !spender ||
42
+ isNativeToken(payTokenId, nativeTokenAddress) ||
43
+ isSwapWrapToken(payTokenId, receiveTokenId, chain, nativeTokenAddress);
44
+ return [
45
+ isSameAddress(routerWhitelist || "", router),
46
+ skipSpender ? true : isSameAddress(spenderWhitelist || "", spender),
47
+ ];
48
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Swap 两端共用的报价校验。
3
+ * wrap / 合约白名单 / calldata 分文件;链目录、预执行留在应用。
4
+ */
5
+ export { isSwapWrapToken } from "./wrap";
6
+ export { getRouter, getSpender, verifyRouterAndSpender } from "./contracts";
7
+ export type { VerifyRouterAndSpenderParams } from "./contracts";
8
+ export { CALLDATA_MIN_RECEIVE_TOLERANCE, decodeSwapCalldata, matchDecodedCalldata, shouldSkipCalldataMatch, verifyCalldata, } from "./calldata";
9
+ export type { VerifyCalldataParams } from "./calldata";
10
+ export { isSwapCalldataReceiverAllowed } from "./receiver";
11
+ export { verifySdk } from "./sdk";
12
+ export type { VerifySdkParams, VerifySdkResult } from "./sdk";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Swap 两端共用的报价校验。
3
+ * wrap / 合约白名单 / calldata 分文件;链目录、预执行留在应用。
4
+ */
5
+ export { isSwapWrapToken } from "./wrap";
6
+ export { getRouter, getSpender, verifyRouterAndSpender } from "./contracts";
7
+ export { CALLDATA_MIN_RECEIVE_TOLERANCE, decodeSwapCalldata, matchDecodedCalldata, shouldSkipCalldataMatch, verifyCalldata, } from "./calldata";
8
+ export { isSwapCalldataReceiverAllowed } from "./receiver";
9
+ export { verifySdk } from "./sdk";
@@ -0,0 +1 @@
1
+ export declare const isSwapCalldataReceiverAllowed: (toTokenReceiver?: string | null, userAddress?: string | null) => boolean;
@@ -0,0 +1,15 @@
1
+ import { NULL_ADDRESS, isSameAddress } from "../utils";
2
+ export const isSwapCalldataReceiverAllowed = (toTokenReceiver, userAddress) => {
3
+ var _a;
4
+ const receiver = ((_a = toTokenReceiver === null || toTokenReceiver === void 0 ? void 0 : toTokenReceiver.toString) === null || _a === void 0 ? void 0 : _a.call(toTokenReceiver)) || "";
5
+ if (!receiver) {
6
+ return true;
7
+ }
8
+ if (isSameAddress(receiver, NULL_ADDRESS)) {
9
+ return true;
10
+ }
11
+ if (!userAddress) {
12
+ return false;
13
+ }
14
+ return isSameAddress(receiver, userAddress);
15
+ };
@@ -0,0 +1,31 @@
1
+ import { CHAINS_ENUM } from "@debank/common";
2
+ import { DEX_ENUM } from "../consts";
3
+ import { DecodeCalldataResult, QuoteResult } from "../quote";
4
+ export type VerifySdkParams = {
5
+ chain: CHAINS_ENUM;
6
+ dexId: DEX_ENUM;
7
+ /** UI 滑点,单位百分数,如 1 表示 1%。 */
8
+ slippage: string | number;
9
+ data: QuoteResult | null;
10
+ payTokenId: string;
11
+ receiveTokenId: string;
12
+ /** 由应用传入,对应其链目录中的 native 地址。 */
13
+ nativeTokenAddress: string;
14
+ /** 解码 calldata 用;应用侧用自己的链目录解析。 */
15
+ chainId: number;
16
+ userAddress: string;
17
+ };
18
+ export type VerifySdkResult = {
19
+ routerPass: boolean;
20
+ spenderPass: boolean;
21
+ callDataPass: boolean;
22
+ receiverPass: boolean;
23
+ isSdkDataPass: boolean;
24
+ decoded: DecodeCalldataResult | null;
25
+ };
26
+ /**
27
+ * 组合 router / spender / calldata。
28
+ * native↔WETH 视为 wrap,走 WRAPTOKEN(不解码、不查 DEX 白名单)。
29
+ * 预执行、gas 不在此处理。
30
+ */
31
+ export declare const verifySdk: (p: VerifySdkParams) => VerifySdkResult;
@@ -0,0 +1,46 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { DEX_ENUM } from "../consts";
3
+ import { decodeSwapCalldata, verifyCalldata } from "./calldata";
4
+ import { verifyRouterAndSpender } from "./contracts";
5
+ import { isSwapCalldataReceiverAllowed } from "./receiver";
6
+ import { isSwapWrapToken } from "./wrap";
7
+ const toTxWithChainId = (data, chainId) => (data === null || data === void 0 ? void 0 : data.tx) ? Object.assign(Object.assign({}, data.tx), { chainId }) : undefined;
8
+ /**
9
+ * 组合 router / spender / calldata。
10
+ * native↔WETH 视为 wrap,走 WRAPTOKEN(不解码、不查 DEX 白名单)。
11
+ * 预执行、gas 不在此处理。
12
+ */
13
+ export const verifySdk = (p) => {
14
+ var _a, _b, _c;
15
+ const actualDexId = isSwapWrapToken(p.payTokenId, p.receiveTokenId, p.chain, p.nativeTokenAddress)
16
+ ? DEX_ENUM.WRAPTOKEN
17
+ : p.dexId;
18
+ const [routerPass, spenderPass] = verifyRouterAndSpender({
19
+ chain: p.chain,
20
+ dexId: actualDexId,
21
+ router: (_b = (_a = p.data) === null || _a === void 0 ? void 0 : _a.tx) === null || _b === void 0 ? void 0 : _b.to,
22
+ spender: (_c = p.data) === null || _c === void 0 ? void 0 : _c.spender,
23
+ payTokenId: p.payTokenId,
24
+ receiveTokenId: p.receiveTokenId,
25
+ nativeTokenAddress: p.nativeTokenAddress,
26
+ });
27
+ const tx = toTxWithChainId(p.data, p.chainId);
28
+ const slippageRatio = new BigNumber(p.slippage).div(100).toFixed();
29
+ const decoded = decodeSwapCalldata(actualDexId, tx);
30
+ const callDataPass = verifyCalldata({
31
+ data: p.data,
32
+ dexId: actualDexId,
33
+ slippage: slippageRatio,
34
+ tx,
35
+ decoded,
36
+ });
37
+ const receiverPass = isSwapCalldataReceiverAllowed(decoded === null || decoded === void 0 ? void 0 : decoded.toTokenReceiver, p.userAddress);
38
+ return {
39
+ routerPass,
40
+ spenderPass,
41
+ callDataPass,
42
+ receiverPass,
43
+ isSdkDataPass: routerPass && spenderPass && callDataPass && receiverPass,
44
+ decoded,
45
+ };
46
+ };
@@ -0,0 +1,6 @@
1
+ import { CHAINS_ENUM } from "@debank/common";
2
+ /**
3
+ * 是否为「链上 native ↔ 包装代币」互换。
4
+ * native 地址由调用方传入,不在库内查链目录(自定义链只存在于应用侧)。
5
+ */
6
+ export declare const isSwapWrapToken: (payTokenId: string, receiveTokenId: string, chain: CHAINS_ENUM, nativeTokenAddress: string) => boolean;
@@ -0,0 +1,17 @@
1
+ import { WrapTokenAddressMap } from "../list";
2
+ import { isSameAddress } from "../utils";
3
+ /**
4
+ * 是否为「链上 native ↔ 包装代币」互换。
5
+ * native 地址由调用方传入,不在库内查链目录(自定义链只存在于应用侧)。
6
+ */
7
+ export const isSwapWrapToken = (payTokenId, receiveTokenId, chain, nativeTokenAddress) => {
8
+ const wrapTokens = [
9
+ WrapTokenAddressMap[chain],
10
+ nativeTokenAddress,
11
+ ];
12
+ return (!!payTokenId &&
13
+ !!receiveTokenId &&
14
+ payTokenId !== receiveTokenId &&
15
+ !!wrapTokens.find((token) => isSameAddress(payTokenId, token)) &&
16
+ !!wrapTokens.find((token) => isSameAddress(receiveTokenId, token)));
17
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rabby-wallet/rabby-swap",
3
- "version": "0.0.85",
3
+ "version": "0.0.86-alpha.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {