@xswap-link/sdk 0.0.13 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main.yml +2 -1
- package/.github/workflows/publish.yml +2 -1
- package/.prettierrc +7 -0
- package/CHANGELOG.md +27 -1
- package/README.md +23 -14
- package/dist/index.css +1108 -0
- package/dist/index.d.mts +212 -52
- package/dist/index.d.ts +212 -52
- package/dist/index.js +2184 -85
- package/dist/index.mjs +2148 -76
- package/nodemon.json +5 -0
- package/package.json +20 -3
- package/postcss.config.js +3 -0
- package/src/components/Alert/index.tsx +9 -0
- package/src/components/Skeleton/index.tsx +10 -0
- package/src/components/TxConfigForm/BalanceComponent.tsx +51 -0
- package/src/components/TxConfigForm/ChainListElement.tsx +36 -0
- package/src/components/TxConfigForm/Description.tsx +15 -0
- package/src/components/TxConfigForm/ErrorField.tsx +17 -0
- package/src/components/TxConfigForm/FeesDetails.tsx +105 -0
- package/src/components/TxConfigForm/Form.tsx +250 -0
- package/src/components/TxConfigForm/History.tsx +155 -0
- package/src/components/TxConfigForm/HistoryCard.tsx +209 -0
- package/src/components/TxConfigForm/Settings.tsx +137 -0
- package/src/components/TxConfigForm/Summary.tsx +57 -0
- package/src/components/TxConfigForm/SwapPanel.tsx +191 -0
- package/src/components/TxConfigForm/TokenPicker.tsx +171 -0
- package/src/components/TxConfigForm/TopBar.tsx +53 -0
- package/src/components/TxConfigForm/index.tsx +121 -0
- package/src/components/global.css +34 -0
- package/src/components/icons/ArrowDownIcon.tsx +16 -0
- package/src/components/icons/ArrowRightIcon.tsx +11 -0
- package/src/components/icons/ArrowUpRightIcon.tsx +11 -0
- package/src/components/icons/ChainlinkCCIPIcon.tsx +23 -0
- package/src/components/icons/CheckIcon.tsx +11 -0
- package/src/components/icons/ChevronDownIcon.tsx +15 -0
- package/src/components/icons/ChevronUpIcon.tsx +15 -0
- package/src/components/icons/CircularProgressIcon.tsx +24 -0
- package/src/components/icons/CloseIcon.tsx +15 -0
- package/src/components/icons/CoinsIcon.tsx +17 -0
- package/src/components/icons/DownArrorIcon.tsx +17 -0
- package/src/components/icons/HistoryIcon.tsx +16 -0
- package/src/components/icons/HourGlassIcon.tsx +11 -0
- package/src/components/icons/InfoIcon.tsx +13 -0
- package/src/components/icons/PercentageIcon.tsx +29 -0
- package/src/components/icons/SearchIcon.tsx +15 -0
- package/src/components/icons/SettingsIcon.tsx +16 -0
- package/src/components/icons/TimerIcon.tsx +15 -0
- package/src/components/icons/XMarkIcon.tsx +11 -0
- package/src/components/icons/XSwapBadgeIcon.tsx +23 -0
- package/src/components/icons/index.ts +20 -0
- package/src/components/index.ts +3 -0
- package/src/config/index.ts +1 -0
- package/src/config/wagmiConfig.ts +45 -0
- package/src/constants/index.ts +9 -5
- package/src/contracts/abi/BatchQuery.json +52 -0
- package/src/contracts/abi/index.ts +2 -0
- package/src/contracts/addresses.ts +34 -0
- package/src/contracts/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useDebounce.tsx +21 -0
- package/src/index.ts +6 -0
- package/src/models/Addresses.ts +12 -0
- package/src/models/Route.ts +17 -5
- package/src/models/TokenData.ts +45 -0
- package/src/models/TransactionHistory.ts +48 -0
- package/src/models/XSwapConfig.ts +3 -0
- package/src/models/forms/TxConfigFormData.ts +8 -0
- package/src/models/forms/index.ts +1 -0
- package/src/models/index.ts +6 -6
- package/src/models/integrations/GenerateStakingCallsParams.ts +8 -0
- package/src/models/integrations/index.ts +1 -0
- package/src/models/payloads/GetPricesPayload.ts +4 -0
- package/src/models/payloads/GetRoutePayload.ts +1 -3
- package/src/models/payloads/GetSwapTxPayload.ts +8 -0
- package/src/models/payloads/index.ts +2 -0
- package/src/services/api.ts +46 -64
- package/src/services/index.ts +1 -0
- package/src/services/integrations/customCalls/index.ts +1 -0
- package/src/services/integrations/customCalls/staking.ts +83 -0
- package/src/services/integrations/index.ts +2 -0
- package/src/services/integrations/transactions.ts +30 -0
- package/src/utils/contracts.ts +153 -11
- package/src/utils/index.ts +49 -1
- package/src/utils/numbers.ts +47 -0
- package/src/utils/strings.ts +6 -0
- package/tailwind.config.js +19 -0
- package/test/api.test.ts +1 -1
- package/tsconfig.json +10 -1
- package/xswap.config.ts +18 -0
- package/index.ts +0 -5
- package/src/models/ApiOverrides.ts +0 -3
- package/src/models/Chain.ts +0 -20
- package/src/models/Prices.ts +0 -9
- package/src/models/Token.ts +0 -10
- package/src/models/XSwapFee.ts +0 -4
- package/src/models/XSwapFees.ts +0 -7
- package/src/utils/bigNumbers.ts +0 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./TxConfigFormData";
|
package/src/models/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./Addresses";
|
|
2
|
+
export * from "./XSwapConfig";
|
|
2
3
|
export * from "./BridgeToken";
|
|
3
|
-
export * from "./
|
|
4
|
+
export * from "./TokenData";
|
|
4
5
|
export * from "./CoinTypeAddress";
|
|
5
6
|
export * from "./CollectFees";
|
|
6
7
|
export * from "./ContractCall";
|
|
7
8
|
export * from "./Ecosystem";
|
|
8
9
|
export * from "./Environment";
|
|
10
|
+
export * from "./forms";
|
|
11
|
+
export * from "./integrations";
|
|
9
12
|
export * from "./payloads";
|
|
10
13
|
export * from "./Protocol";
|
|
11
14
|
export * from "./Route";
|
|
12
|
-
export * from "./Token";
|
|
13
15
|
export * from "./Web3Environment";
|
|
14
16
|
export * from "./XSwapCallType";
|
|
15
|
-
export * from "./XSwapFee";
|
|
16
|
-
export * from "./XSwapFees";
|
|
17
17
|
export * from "./Referral";
|
|
18
|
-
export * from "./
|
|
18
|
+
export * from "./TransactionHistory";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./GenerateStakingCallsParams";
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { CoinTypeAddress } from "
|
|
2
|
-
import { CollectFees } from "../CollectFees";
|
|
3
|
-
import { ContractCall } from "../ContractCall";
|
|
1
|
+
import { CoinTypeAddress, CollectFees, ContractCall } from "@src/models";
|
|
4
2
|
|
|
5
3
|
export type GetRoutePayload = {
|
|
6
4
|
fromChain: string;
|
package/src/services/api.ts
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
|
+
import xSwapConfig from "../../xswap.config";
|
|
1
2
|
import {
|
|
2
|
-
ApiOverrides,
|
|
3
3
|
BridgeToken,
|
|
4
4
|
Chain,
|
|
5
5
|
Ecosystem,
|
|
6
|
+
GetPricesPayload,
|
|
6
7
|
GetRoutePayload,
|
|
7
8
|
Route,
|
|
8
9
|
Token,
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
TokenPrices,
|
|
11
|
+
TransactionHistory,
|
|
12
|
+
} from "@src/models";
|
|
11
13
|
|
|
12
14
|
async function _sendRequest<T>(
|
|
13
15
|
urlPath: string,
|
|
14
|
-
options
|
|
15
|
-
overrides?: ApiOverrides
|
|
16
|
+
options?: RequestInit,
|
|
16
17
|
): Promise<T> {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
...options,
|
|
18
|
+
const response = await fetch(`${xSwapConfig.apiUrl}${urlPath}`, {
|
|
19
|
+
method: "GET",
|
|
20
20
|
headers: {
|
|
21
21
|
"Content-Type": "application/json",
|
|
22
|
-
|
|
22
|
+
"Cache-Control": "no-cache",
|
|
23
|
+
...options?.headers,
|
|
23
24
|
},
|
|
25
|
+
...options,
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
if (!response.ok) {
|
|
27
29
|
throw new Error(
|
|
28
|
-
`API request failed with status ${response.status}: ${response.statusText}
|
|
30
|
+
`API request failed with status ${response.status}: ${response.statusText}`,
|
|
29
31
|
);
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -35,61 +37,37 @@ async function _sendRequest<T>(
|
|
|
35
37
|
/**
|
|
36
38
|
* Get swap route based on provided criteria.
|
|
37
39
|
* @param payload required
|
|
38
|
-
* @param overrides optional - e.g. {apiUrl}
|
|
39
40
|
*/
|
|
40
|
-
export async function getRoute(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"/route",
|
|
46
|
-
{
|
|
47
|
-
method: "POST",
|
|
48
|
-
body: JSON.stringify(payload),
|
|
49
|
-
},
|
|
50
|
-
overrides
|
|
51
|
-
);
|
|
41
|
+
export async function getRoute(payload: GetRoutePayload): Promise<Route> {
|
|
42
|
+
return _sendRequest<Route>("/route", {
|
|
43
|
+
method: "POST",
|
|
44
|
+
body: JSON.stringify(payload),
|
|
45
|
+
});
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
/**
|
|
55
49
|
* GET chains based on provided criteria.
|
|
56
50
|
* @param ecosystem optional
|
|
57
|
-
* @param overrides optional - e.g. {apiUrl}
|
|
58
51
|
*/
|
|
59
|
-
export async function getChains(
|
|
60
|
-
ecosystem
|
|
61
|
-
|
|
62
|
-
): Promise<Chain[]> {
|
|
63
|
-
const data = { ecosystem };
|
|
64
|
-
|
|
65
|
-
return _sendRequest<Chain[]>(
|
|
66
|
-
`/chains?data=${JSON.stringify(data)}`,
|
|
67
|
-
{
|
|
68
|
-
method: "GET",
|
|
69
|
-
},
|
|
70
|
-
overrides
|
|
71
|
-
);
|
|
52
|
+
export async function getChains(ecosystem?: Ecosystem): Promise<Chain[]> {
|
|
53
|
+
return _sendRequest<Chain[]>(`/chains?data=${JSON.stringify({ ecosystem })}
|
|
54
|
+
`);
|
|
72
55
|
}
|
|
73
56
|
|
|
74
57
|
/**
|
|
75
58
|
* GET chain data based on provided criteria.
|
|
76
59
|
* @param chainId
|
|
77
60
|
* @param ecosystem optional - Default: {@link DEFAULT_ECOSYSTEM}
|
|
78
|
-
* @param overrides optional - e.g. {apiUrl}
|
|
79
61
|
*/
|
|
80
62
|
export async function getChainData(
|
|
81
63
|
chainId?: string,
|
|
82
64
|
ecosystem?: Ecosystem,
|
|
83
|
-
overrides?: ApiOverrides
|
|
84
65
|
): Promise<Chain> {
|
|
85
|
-
const data = { chainId, ecosystem };
|
|
86
|
-
|
|
87
66
|
return _sendRequest<Chain>(
|
|
88
|
-
`/chains?data=${JSON.stringify(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
overrides
|
|
67
|
+
`/chains?data=${JSON.stringify({
|
|
68
|
+
chainId,
|
|
69
|
+
ecosystem,
|
|
70
|
+
})}`,
|
|
93
71
|
);
|
|
94
72
|
}
|
|
95
73
|
|
|
@@ -98,33 +76,37 @@ export async function getChainData(
|
|
|
98
76
|
* @param chainId required
|
|
99
77
|
* @param address optional
|
|
100
78
|
* @param ecosystem optional but {@link DEFAULT_ECOSYSTEM} will be considered
|
|
101
|
-
* @param overrides optional - e.g. {apiUrl}
|
|
102
79
|
*/
|
|
103
80
|
export async function getTokens(
|
|
104
81
|
chainId: string,
|
|
105
82
|
address?: string,
|
|
106
83
|
ecosystem?: Ecosystem,
|
|
107
|
-
overrides?: ApiOverrides
|
|
108
84
|
): Promise<Token[]> {
|
|
109
|
-
const data = { chainId, address, ecosystem };
|
|
110
|
-
|
|
111
85
|
return _sendRequest<Token[]>(
|
|
112
|
-
`/tokens?data=${JSON.stringify(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
86
|
+
`/tokens?data=${JSON.stringify({
|
|
87
|
+
chainId,
|
|
88
|
+
address,
|
|
89
|
+
ecosystem,
|
|
90
|
+
})}`,
|
|
117
91
|
);
|
|
118
92
|
}
|
|
119
93
|
|
|
120
|
-
export async function getBridgeTokens(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
94
|
+
export async function getBridgeTokens(): Promise<BridgeToken[]> {
|
|
95
|
+
return _sendRequest<BridgeToken[]>(`/bridgeTokens`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function getHistory(payload: { walletAddress: string }) {
|
|
99
|
+
return await _sendRequest<TransactionHistory>(
|
|
100
|
+
`/history?${new URLSearchParams({
|
|
101
|
+
data: JSON.stringify(payload),
|
|
102
|
+
})}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function getPrices(payload: GetPricesPayload) {
|
|
107
|
+
return await _sendRequest<TokenPrices>(
|
|
108
|
+
`/tokenPrices?${new URLSearchParams({
|
|
109
|
+
data: JSON.stringify(payload),
|
|
110
|
+
})}`,
|
|
129
111
|
);
|
|
130
112
|
}
|
package/src/services/index.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./staking";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import {
|
|
3
|
+
ContractCall,
|
|
4
|
+
GenerateStakingCallsParams,
|
|
5
|
+
XSwapCallType,
|
|
6
|
+
} from "@src/models";
|
|
7
|
+
import {
|
|
8
|
+
findPlaceholderIndex,
|
|
9
|
+
generateUniqueRandomBigNumber,
|
|
10
|
+
IERC20,
|
|
11
|
+
replaceNull,
|
|
12
|
+
} from "@src/utils";
|
|
13
|
+
import { ERC20Abi } from "@src/contracts";
|
|
14
|
+
|
|
15
|
+
export const generateStakingCalls = ({
|
|
16
|
+
token,
|
|
17
|
+
staking,
|
|
18
|
+
stakingAbi,
|
|
19
|
+
stakingFunName,
|
|
20
|
+
stakingFunParams,
|
|
21
|
+
}: GenerateStakingCallsParams): ContractCall[] => {
|
|
22
|
+
const flatStakingFunParams = stakingFunParams.flat(Infinity);
|
|
23
|
+
|
|
24
|
+
if (flatStakingFunParams.filter((param) => param === null).length !== 1) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"Exactly 1 'null' value should be provided in 'funParams' in order to override token balance",
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const randomBigNumber = generateUniqueRandomBigNumber(
|
|
31
|
+
32,
|
|
32
|
+
flatStakingFunParams,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const modifiedStakingFunParams = replaceNull(
|
|
36
|
+
stakingFunParams,
|
|
37
|
+
randomBigNumber,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const approveFunParams = [staking, randomBigNumber];
|
|
41
|
+
const stakingApproveCall: ContractCall = {
|
|
42
|
+
callType: XSwapCallType.FULL_TOKEN_BALANCE,
|
|
43
|
+
target: token,
|
|
44
|
+
callData: IERC20.encodeFunctionData("approve", approveFunParams),
|
|
45
|
+
payload: ethers.utils.defaultAbiCoder.encode(
|
|
46
|
+
["address", "uint256"],
|
|
47
|
+
[
|
|
48
|
+
token,
|
|
49
|
+
findPlaceholderIndex(
|
|
50
|
+
JSON.stringify(ERC20Abi),
|
|
51
|
+
"approve",
|
|
52
|
+
approveFunParams,
|
|
53
|
+
randomBigNumber,
|
|
54
|
+
),
|
|
55
|
+
],
|
|
56
|
+
),
|
|
57
|
+
value: "0",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const stakingCall: ContractCall = {
|
|
61
|
+
callType: XSwapCallType.FULL_TOKEN_BALANCE,
|
|
62
|
+
target: staking,
|
|
63
|
+
callData: new ethers.utils.Interface(stakingAbi).encodeFunctionData(
|
|
64
|
+
stakingFunName,
|
|
65
|
+
modifiedStakingFunParams,
|
|
66
|
+
),
|
|
67
|
+
payload: ethers.utils.defaultAbiCoder.encode(
|
|
68
|
+
["address", "uint256"],
|
|
69
|
+
[
|
|
70
|
+
token,
|
|
71
|
+
findPlaceholderIndex(
|
|
72
|
+
stakingAbi,
|
|
73
|
+
stakingFunName,
|
|
74
|
+
modifiedStakingFunParams,
|
|
75
|
+
randomBigNumber,
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
),
|
|
79
|
+
value: "0",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return [stakingApproveCall, stakingCall];
|
|
83
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Chain,
|
|
3
|
+
GetSwapTxPayload,
|
|
4
|
+
Transactions,
|
|
5
|
+
Web3Environment,
|
|
6
|
+
} from "@src/models";
|
|
7
|
+
import { getChains } from "@src/services";
|
|
8
|
+
import { openTxConfigForm } from "@src/components";
|
|
9
|
+
|
|
10
|
+
export const getSwapTx = async ({
|
|
11
|
+
dstChain,
|
|
12
|
+
dstToken,
|
|
13
|
+
customContractCalls = [],
|
|
14
|
+
desc,
|
|
15
|
+
}: GetSwapTxPayload): Promise<Transactions> => {
|
|
16
|
+
const supportedChains: Chain[] = (await getChains()).filter(
|
|
17
|
+
({ web3Environment, swapSupported }) =>
|
|
18
|
+
web3Environment === Web3Environment.MAINNET && swapSupported,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const route = await openTxConfigForm({
|
|
22
|
+
dstChainId: dstChain,
|
|
23
|
+
dstTokenAddr: dstToken,
|
|
24
|
+
customContractCalls,
|
|
25
|
+
desc,
|
|
26
|
+
supportedChains,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return route.transactions;
|
|
30
|
+
};
|
package/src/utils/contracts.ts
CHANGED
|
@@ -1,19 +1,161 @@
|
|
|
1
|
-
import { BigNumberish, ethers } from "ethers";
|
|
2
|
-
import {
|
|
3
|
-
import { safeBigNumberFrom } from "./
|
|
1
|
+
import { BigNumber, BigNumberish, ethers } from "ethers";
|
|
2
|
+
import { TransactionRequest } from "@ethersproject/providers";
|
|
3
|
+
import { safeBigNumberFrom } from "./numbers";
|
|
4
|
+
import { ADDRESSES, BatchQueryAbi, ERC20Abi } from "@src/contracts";
|
|
5
|
+
import { Chain, TokenBalances } from "@src/models";
|
|
6
|
+
import { chunkArray } from "@src/utils";
|
|
7
|
+
import { BALANCES_CHUNK_SIZE } from "@src/constants";
|
|
4
8
|
|
|
5
|
-
const
|
|
9
|
+
export const IERC20 = new ethers.utils.Interface(ERC20Abi);
|
|
10
|
+
|
|
11
|
+
export const getBalanceOf = async (
|
|
12
|
+
wallet: string,
|
|
13
|
+
token: string,
|
|
14
|
+
rpcUrl: string,
|
|
15
|
+
): Promise<string> => {
|
|
16
|
+
const provider = ethers.getDefaultProvider(rpcUrl);
|
|
17
|
+
|
|
18
|
+
if (token === ethers.constants.AddressZero) {
|
|
19
|
+
return ethers.utils.formatEther(await provider.getBalance(wallet));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const contract = new ethers.Contract(token, ERC20Abi, provider);
|
|
23
|
+
return ethers.utils.formatUnits(
|
|
24
|
+
await contract.balanceOf(wallet),
|
|
25
|
+
await contract.decimals(),
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const getBalances = async (
|
|
30
|
+
chain: Chain,
|
|
31
|
+
wallet: string,
|
|
32
|
+
): Promise<TokenBalances> => {
|
|
33
|
+
return {
|
|
34
|
+
...(await getNativeBalance(chain, wallet)),
|
|
35
|
+
...(await getErc20Balances(chain, wallet)),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getNativeBalance = async (
|
|
40
|
+
chain: Chain,
|
|
41
|
+
wallet: string,
|
|
42
|
+
): Promise<TokenBalances> => {
|
|
43
|
+
const native = chain.tokens.find(
|
|
44
|
+
({ address }) => address === ethers.constants.AddressZero,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (native) {
|
|
48
|
+
const provider = ethers.getDefaultProvider(chain.publicRpcUrls[0]);
|
|
49
|
+
const balance = await provider.getBalance(wallet);
|
|
50
|
+
return {
|
|
51
|
+
[native.address]: balance,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const getErc20Balances = async (
|
|
58
|
+
chain: Chain,
|
|
59
|
+
wallet: string,
|
|
60
|
+
): Promise<TokenBalances> => {
|
|
61
|
+
const erc20Tokens = chain.tokens.filter(
|
|
62
|
+
({ address }) => address !== ethers.constants.AddressZero,
|
|
63
|
+
);
|
|
64
|
+
const tokenChunks = chunkArray(erc20Tokens, BALANCES_CHUNK_SIZE);
|
|
65
|
+
const promises = tokenChunks.map(async (tokenChunk) => {
|
|
66
|
+
const tokenAddresses = tokenChunk.map((token) => token.address);
|
|
67
|
+
const calldatas = tokenChunk.map(() =>
|
|
68
|
+
IERC20.encodeFunctionData("balanceOf", [wallet]),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const contractAddress = ADDRESSES[chain.chainId]?.BatchQuery;
|
|
72
|
+
const rpcUrl = chain.publicRpcUrls[0];
|
|
73
|
+
if (contractAddress && rpcUrl) {
|
|
74
|
+
const contract = new ethers.Contract(
|
|
75
|
+
contractAddress,
|
|
76
|
+
BatchQueryAbi,
|
|
77
|
+
ethers.getDefaultProvider(rpcUrl),
|
|
78
|
+
);
|
|
79
|
+
return await contract["batchQuery"](tokenAddresses, calldatas);
|
|
80
|
+
}
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const tokenBalances: BigNumber[] = (await Promise.all(promises))
|
|
85
|
+
.flat()
|
|
86
|
+
.map(
|
|
87
|
+
(encodedBalance) =>
|
|
88
|
+
ethers.utils.defaultAbiCoder.decode(["uint256"], encodedBalance)[0],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const balances: TokenBalances = {};
|
|
92
|
+
|
|
93
|
+
erc20Tokens.forEach((token, index) => {
|
|
94
|
+
balances[token.address] = tokenBalances[index];
|
|
95
|
+
});
|
|
96
|
+
return balances;
|
|
97
|
+
};
|
|
6
98
|
|
|
7
99
|
export const generateApproveTxData = (
|
|
8
100
|
tokenAddress: string,
|
|
9
101
|
spender: string,
|
|
10
|
-
amount: BigNumberish
|
|
102
|
+
amount: BigNumberish,
|
|
103
|
+
): TransactionRequest | undefined => {
|
|
104
|
+
return tokenAddress !== ethers.constants.AddressZero
|
|
105
|
+
? {
|
|
106
|
+
to: tokenAddress,
|
|
107
|
+
value: safeBigNumberFrom("0"),
|
|
108
|
+
data: IERC20.encodeFunctionData("approve", [spender, amount]),
|
|
109
|
+
}
|
|
110
|
+
: undefined;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Calculate param's value offset within the encoded function's bytecode.
|
|
115
|
+
* @param abi
|
|
116
|
+
* @param funName
|
|
117
|
+
* @param funParams
|
|
118
|
+
* @param placeholderValue
|
|
119
|
+
*/
|
|
120
|
+
export const findPlaceholderIndex = (
|
|
121
|
+
abi: string,
|
|
122
|
+
funName: string,
|
|
123
|
+
// eslint-disable-next-line
|
|
124
|
+
funParams: any[],
|
|
125
|
+
placeholderValue: BigNumber,
|
|
11
126
|
) => {
|
|
12
|
-
|
|
127
|
+
if (
|
|
128
|
+
funParams.flat(Infinity).filter((param) => param === placeholderValue)
|
|
129
|
+
.length !== 1
|
|
130
|
+
) {
|
|
131
|
+
throw new Error("Random placeholder value must be provided and unique.");
|
|
132
|
+
}
|
|
13
133
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
134
|
+
const iface = new ethers.utils.Interface(abi);
|
|
135
|
+
const functionFragment = iface.getFunction(funName);
|
|
136
|
+
|
|
137
|
+
if (!functionFragment) {
|
|
138
|
+
throw new Error(`Can't find function "${funName}" in provided ABI.`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const encodedFunData = iface.encodeFunctionData(functionFragment, funParams);
|
|
142
|
+
|
|
143
|
+
// Remove the 4-byte selector
|
|
144
|
+
const paramData = encodedFunData.slice(10);
|
|
145
|
+
|
|
146
|
+
// Split into 32-byte (64 hex characters) chunks
|
|
147
|
+
const chunks: string[] = [];
|
|
148
|
+
for (let i = 0; i < paramData.length; i += 64) {
|
|
149
|
+
chunks.push(paramData.slice(i, i + 64));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Find the index of the chunk containing the random value
|
|
153
|
+
const searchValue = placeholderValue.toHexString().slice(2).padStart(64, "0");
|
|
154
|
+
const result = chunks.findIndex((chunk) => chunk === searchValue);
|
|
155
|
+
|
|
156
|
+
if (result === -1) {
|
|
157
|
+
throw new Error("Randomized parameter not found in the encoded data.");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
19
161
|
};
|
package/src/utils/index.ts
CHANGED
|
@@ -1,2 +1,50 @@
|
|
|
1
|
-
export * from "./bigNumbers";
|
|
2
1
|
export * from "./contracts";
|
|
2
|
+
export * from "./numbers";
|
|
3
|
+
export * from "./strings";
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line
|
|
6
|
+
export const replaceNull = (values: any[], newValue: any) => {
|
|
7
|
+
const modifiedValues = [...values];
|
|
8
|
+
for (let i = 0; i < modifiedValues.length; i++) {
|
|
9
|
+
if (Array.isArray(modifiedValues[i])) {
|
|
10
|
+
modifiedValues[i] = replaceNull(modifiedValues[i], newValue);
|
|
11
|
+
} else {
|
|
12
|
+
if (modifiedValues[i] === null) {
|
|
13
|
+
modifiedValues[i] = newValue;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return modifiedValues;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const deepMergeObjects = (
|
|
21
|
+
// eslint-disable-next-line
|
|
22
|
+
obj1: Record<string, any>,
|
|
23
|
+
// eslint-disable-next-line
|
|
24
|
+
obj2: Record<string, any>,
|
|
25
|
+
) => {
|
|
26
|
+
for (const key in obj2) {
|
|
27
|
+
if (obj2.hasOwnProperty(key)) {
|
|
28
|
+
if (
|
|
29
|
+
obj1.hasOwnProperty(key) &&
|
|
30
|
+
typeof obj1[key] === "object" &&
|
|
31
|
+
typeof obj2[key] === "object"
|
|
32
|
+
) {
|
|
33
|
+
deepMergeObjects(obj1[key], obj2[key]);
|
|
34
|
+
} else {
|
|
35
|
+
obj1[key] = obj2[key];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { ...obj1 };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line
|
|
43
|
+
export const chunkArray = (array: any[], chunkSize: number): any[][] => {
|
|
44
|
+
// eslint-disable-next-line
|
|
45
|
+
const result: any[][] = [];
|
|
46
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
47
|
+
result.push(array.slice(i, i + chunkSize));
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BigNumber as BigNumberJS } from "bignumber.js";
|
|
2
|
+
import { BigNumber, utils } from "ethers";
|
|
3
|
+
|
|
4
|
+
export const safeBigNumberFrom = (value: string) => {
|
|
5
|
+
BigNumberJS.config({ DECIMAL_PLACES: 0 });
|
|
6
|
+
return BigNumber.from(new BigNumberJS(value).div(1).toFixed());
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const weiToHumanReadable = ({
|
|
10
|
+
amount,
|
|
11
|
+
decimals,
|
|
12
|
+
precisionFractionalPlaces,
|
|
13
|
+
prettifySmallNumber = false,
|
|
14
|
+
}: {
|
|
15
|
+
amount: string;
|
|
16
|
+
decimals: number;
|
|
17
|
+
precisionFractionalPlaces: number;
|
|
18
|
+
prettifySmallNumber?: boolean;
|
|
19
|
+
}): string => {
|
|
20
|
+
const decimalsFactor = Math.pow(10, decimals);
|
|
21
|
+
|
|
22
|
+
BigNumberJS.config({ DECIMAL_PLACES: precisionFractionalPlaces });
|
|
23
|
+
|
|
24
|
+
const res = new BigNumberJS(amount).div(decimalsFactor).toFixed();
|
|
25
|
+
if (prettifySmallNumber && res === "0" && amount !== "0") {
|
|
26
|
+
return `<${(1 / 10 ** precisionFractionalPlaces).toFixed(
|
|
27
|
+
precisionFractionalPlaces,
|
|
28
|
+
)}`;
|
|
29
|
+
}
|
|
30
|
+
return res;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const generateRandomBigNumber = (length: number) => {
|
|
34
|
+
return BigNumber.from(utils.randomBytes(length));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const generateUniqueRandomBigNumber = (
|
|
38
|
+
length: number,
|
|
39
|
+
// eslint-disable-next-line
|
|
40
|
+
existingValues: any[],
|
|
41
|
+
) => {
|
|
42
|
+
let result;
|
|
43
|
+
while (result === undefined || existingValues.includes(result)) {
|
|
44
|
+
result = generateRandomBigNumber(length);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @type {import("tailwindcss").Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
content: ["./src/components/**/*.{js,ts,jsx,tsx,mdx}"],
|
|
4
|
+
theme: {
|
|
5
|
+
extend: {
|
|
6
|
+
colors: {
|
|
7
|
+
x_alert: "rgb(255,183,77)",
|
|
8
|
+
x_alert_light: "rgb(255,226,183)",
|
|
9
|
+
x_blue: "#3396FF",
|
|
10
|
+
x_green: "#66bb6a",
|
|
11
|
+
x_error_background: "rgba(255,202,40,0.1)",
|
|
12
|
+
x_error_border: "rgba(255,111,0,1)",
|
|
13
|
+
x_blue_light: "rgba(54,129,198,1)",
|
|
14
|
+
x_blue_dark: "rgba(43,74,157,1)",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
plugins: [],
|
|
19
|
+
};
|