@tcswap/helpers 4.5.15
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/dist/api/index.cjs +4 -0
- package/dist/api/index.cjs.map +16 -0
- package/dist/api/index.js +4 -0
- package/dist/api/index.js.map +16 -0
- package/dist/chunk-pfmeq01a.js +5 -0
- package/dist/chunk-pfmeq01a.js.map +9 -0
- package/dist/chunk-vb4wtm2w.js +4 -0
- package/dist/chunk-vb4wtm2w.js.map +9 -0
- package/dist/contracts.cjs +4 -0
- package/dist/contracts.cjs.map +10 -0
- package/dist/contracts.js +4 -0
- package/dist/contracts.js.map +10 -0
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +30 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +30 -0
- package/dist/tokens.cjs +4 -0
- package/dist/tokens.cjs.map +10 -0
- package/dist/tokens.js +4 -0
- package/dist/tokens.js.map +10 -0
- package/dist/types/api/index.d.ts +502 -0
- package/dist/types/api/index.d.ts.map +1 -0
- package/dist/types/api/memoless/endpoints.d.ts +56 -0
- package/dist/types/api/memoless/endpoints.d.ts.map +1 -0
- package/dist/types/api/memoless/types.d.ts +85 -0
- package/dist/types/api/memoless/types.d.ts.map +1 -0
- package/dist/types/api/midgard/endpoints.d.ts +80 -0
- package/dist/types/api/midgard/endpoints.d.ts.map +1 -0
- package/dist/types/api/midgard/types.d.ts +543 -0
- package/dist/types/api/midgard/types.d.ts.map +1 -0
- package/dist/types/api/thornode/endpoints.d.ts +34 -0
- package/dist/types/api/thornode/endpoints.d.ts.map +1 -0
- package/dist/types/api/thornode/types.d.ts +264 -0
- package/dist/types/api/thornode/types.d.ts.map +1 -0
- package/dist/types/api/uswap/endpoints.d.ts +372 -0
- package/dist/types/api/uswap/endpoints.d.ts.map +1 -0
- package/dist/types/api/uswap/types.d.ts +1487 -0
- package/dist/types/api/uswap/types.d.ts.map +1 -0
- package/dist/types/contracts.d.ts +2 -0
- package/dist/types/contracts.d.ts.map +1 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/modules/assetValue.d.ts +82 -0
- package/dist/types/modules/assetValue.d.ts.map +1 -0
- package/dist/types/modules/bigIntArithmetics.d.ts +60 -0
- package/dist/types/modules/bigIntArithmetics.d.ts.map +1 -0
- package/dist/types/modules/feeMultiplier.d.ts +48 -0
- package/dist/types/modules/feeMultiplier.d.ts.map +1 -0
- package/dist/types/modules/requestClient.d.ts +33 -0
- package/dist/types/modules/requestClient.d.ts.map +1 -0
- package/dist/types/modules/uSwapConfig.d.ts +249 -0
- package/dist/types/modules/uSwapConfig.d.ts.map +1 -0
- package/dist/types/modules/uSwapError.d.ts +879 -0
- package/dist/types/modules/uSwapError.d.ts.map +1 -0
- package/dist/types/modules/uSwapNumber.d.ts +10 -0
- package/dist/types/modules/uSwapNumber.d.ts.map +1 -0
- package/dist/types/tokens.d.ts +2 -0
- package/dist/types/tokens.d.ts.map +1 -0
- package/dist/types/types/commonTypes.d.ts +16 -0
- package/dist/types/types/commonTypes.d.ts.map +1 -0
- package/dist/types/types/derivationPath.d.ts +4 -0
- package/dist/types/types/derivationPath.d.ts.map +1 -0
- package/dist/types/types/errors/apiV1.d.ts +2 -0
- package/dist/types/types/errors/apiV1.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +6 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/quotes.d.ts +180 -0
- package/dist/types/types/quotes.d.ts.map +1 -0
- package/dist/types/types/sdk.d.ts +35 -0
- package/dist/types/types/sdk.d.ts.map +1 -0
- package/dist/types/types/wallet.d.ts +130 -0
- package/dist/types/types/wallet.d.ts.map +1 -0
- package/dist/types/utils/asset.d.ts +37 -0
- package/dist/types/utils/asset.d.ts.map +1 -0
- package/dist/types/utils/chains.d.ts +13 -0
- package/dist/types/utils/chains.d.ts.map +1 -0
- package/dist/types/utils/derivationPath.d.ts +21 -0
- package/dist/types/utils/derivationPath.d.ts.map +1 -0
- package/dist/types/utils/explorerUrls.d.ts +10 -0
- package/dist/types/utils/explorerUrls.d.ts.map +1 -0
- package/dist/types/utils/liquidity.d.ts +62 -0
- package/dist/types/utils/liquidity.d.ts.map +1 -0
- package/dist/types/utils/memo.d.ts +65 -0
- package/dist/types/utils/memo.d.ts.map +1 -0
- package/dist/types/utils/others.d.ts +15 -0
- package/dist/types/utils/others.d.ts.map +1 -0
- package/dist/types/utils/validators.d.ts +6 -0
- package/dist/types/utils/validators.d.ts.map +1 -0
- package/dist/types/utils/wallets.d.ts +36 -0
- package/dist/types/utils/wallets.d.ts.map +1 -0
- package/package.json +67 -0
- package/src/api/index.ts +15 -0
- package/src/api/memoless/endpoints.ts +62 -0
- package/src/api/memoless/types.ts +83 -0
- package/src/api/midgard/endpoints.ts +352 -0
- package/src/api/midgard/types.ts +515 -0
- package/src/api/thornode/endpoints.ts +109 -0
- package/src/api/thornode/types.ts +247 -0
- package/src/api/uswap/endpoints.ts +252 -0
- package/src/api/uswap/types.ts +626 -0
- package/src/contracts.ts +1 -0
- package/src/index.ts +32 -0
- package/src/modules/__tests__/assetValue.test.ts +2452 -0
- package/src/modules/__tests__/bigIntArithmetics.test.ts +410 -0
- package/src/modules/__tests__/feeMultiplier.test.ts +131 -0
- package/src/modules/__tests__/uSwapConfig.test.ts +429 -0
- package/src/modules/__tests__/uSwapNumber.test.ts +439 -0
- package/src/modules/assetValue.ts +536 -0
- package/src/modules/bigIntArithmetics.ts +366 -0
- package/src/modules/feeMultiplier.ts +84 -0
- package/src/modules/requestClient.ts +116 -0
- package/src/modules/uSwapConfig.ts +189 -0
- package/src/modules/uSwapError.ts +474 -0
- package/src/modules/uSwapNumber.ts +17 -0
- package/src/tokens.ts +1 -0
- package/src/types/commonTypes.ts +10 -0
- package/src/types/derivationPath.ts +11 -0
- package/src/types/errors/apiV1.ts +0 -0
- package/src/types/index.ts +5 -0
- package/src/types/quotes.ts +182 -0
- package/src/types/sdk.ts +38 -0
- package/src/types/wallet.ts +124 -0
- package/src/utils/__tests__/asset.test.ts +186 -0
- package/src/utils/__tests__/derivationPath.test.ts +142 -0
- package/src/utils/__tests__/explorerUrls.test.ts +59 -0
- package/src/utils/__tests__/liquidity.test.ts +302 -0
- package/src/utils/__tests__/memo.test.ts +99 -0
- package/src/utils/__tests__/others.test.ts +169 -0
- package/src/utils/__tests__/validators.test.ts +84 -0
- package/src/utils/__tests__/wallets.test.ts +625 -0
- package/src/utils/asset.ts +399 -0
- package/src/utils/chains.ts +104 -0
- package/src/utils/derivationPath.ts +101 -0
- package/src/utils/explorerUrls.ts +32 -0
- package/src/utils/liquidity.ts +154 -0
- package/src/utils/memo.ts +102 -0
- package/src/utils/others.ts +64 -0
- package/src/utils/validators.ts +36 -0
- package/src/utils/wallets.ts +238 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modifications © 2025 Horizontal Systems.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// @ts-nocheck - Test file with intentional mocking of browser globals and ethers types
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
7
|
+
import { Chain } from "@tcswap/types";
|
|
8
|
+
|
|
9
|
+
import { USwapError } from "../../modules/uSwapError";
|
|
10
|
+
import { WalletOption } from "../../types";
|
|
11
|
+
import {
|
|
12
|
+
addAccountsChangedCallback,
|
|
13
|
+
addEVMWalletNetwork,
|
|
14
|
+
filterSupportedChains,
|
|
15
|
+
getEIP6963Wallets,
|
|
16
|
+
getETHDefaultWallet,
|
|
17
|
+
isDetected,
|
|
18
|
+
isWeb3Detected,
|
|
19
|
+
listWeb3EVMWallets,
|
|
20
|
+
okxMobileEnabled,
|
|
21
|
+
prepareNetworkSwitch,
|
|
22
|
+
providerRequest,
|
|
23
|
+
switchEVMWalletNetwork,
|
|
24
|
+
wrapMethodWithNetworkSwitch,
|
|
25
|
+
} from "../wallets";
|
|
26
|
+
|
|
27
|
+
// Store original globals
|
|
28
|
+
const originalWindow = globalThis.window;
|
|
29
|
+
const originalNavigator = globalThis.navigator;
|
|
30
|
+
|
|
31
|
+
function createMockWindow(overrides: Record<string, any> = {}) {
|
|
32
|
+
return {
|
|
33
|
+
$onekey: undefined,
|
|
34
|
+
addEventListener: mock(() => {}),
|
|
35
|
+
bitkeep: undefined,
|
|
36
|
+
braveSolana: undefined,
|
|
37
|
+
coinbaseWalletExtension: undefined,
|
|
38
|
+
ctrl: undefined,
|
|
39
|
+
dispatchEvent: mock(() => true),
|
|
40
|
+
ethereum: undefined,
|
|
41
|
+
removeEventListener: mock(() => {}),
|
|
42
|
+
trustwallet: undefined,
|
|
43
|
+
vultisig: undefined,
|
|
44
|
+
...overrides,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createMockEthereumProvider(overrides: Record<string, any> = {}) {
|
|
49
|
+
return { on: mock(() => {}), removeListener: mock(() => {}), request: mock(() => Promise.resolve()), ...overrides };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createMockBrowserProvider(overrides: Record<string, any> = {}) {
|
|
53
|
+
return {
|
|
54
|
+
getNetwork: mock(() => Promise.resolve({ chainId: BigInt(1) })),
|
|
55
|
+
send: mock(() => Promise.resolve()),
|
|
56
|
+
...overrides,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("wallets", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
globalThis.window = createMockWindow();
|
|
63
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0" };
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
globalThis.window = originalWindow;
|
|
68
|
+
globalThis.navigator = originalNavigator;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("isWeb3Detected", () => {
|
|
72
|
+
test("returns false when window.ethereum is undefined", () => {
|
|
73
|
+
expect(isWeb3Detected()).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("returns true when window.ethereum is defined", () => {
|
|
77
|
+
globalThis.window.ethereum = createMockEthereumProvider();
|
|
78
|
+
expect(isWeb3Detected()).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("listWeb3EVMWallets", () => {
|
|
83
|
+
test("returns empty array when no wallets detected", () => {
|
|
84
|
+
expect(listWeb3EVMWallets()).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("detects MetaMask wallet", () => {
|
|
88
|
+
globalThis.window.ethereum = createMockEthereumProvider({ isBraveWallet: false });
|
|
89
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.METAMASK);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("detects Brave wallet", () => {
|
|
93
|
+
globalThis.window.ethereum = createMockEthereumProvider({ isBraveWallet: true });
|
|
94
|
+
const wallets = listWeb3EVMWallets();
|
|
95
|
+
expect(wallets).toContain(WalletOption.BRAVE);
|
|
96
|
+
expect(wallets).not.toContain(WalletOption.METAMASK);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("detects Trust wallet via isTrust", () => {
|
|
100
|
+
globalThis.window.ethereum = createMockEthereumProvider({ isTrust: true });
|
|
101
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.TRUSTWALLET_WEB);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("detects Trust wallet via window.trustwallet", () => {
|
|
105
|
+
globalThis.window.trustwallet = createMockEthereumProvider();
|
|
106
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.TRUSTWALLET_WEB);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("detects Coinbase wallet via selectedProvider", () => {
|
|
110
|
+
globalThis.window.ethereum = createMockEthereumProvider({
|
|
111
|
+
overrideIsMetaMask: true,
|
|
112
|
+
selectedProvider: { isCoinbaseWallet: true },
|
|
113
|
+
});
|
|
114
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.COINBASE_WEB);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("detects Coinbase wallet via coinbaseWalletExtension", () => {
|
|
118
|
+
globalThis.window.coinbaseWalletExtension = createMockEthereumProvider();
|
|
119
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.COINBASE_WEB);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("detects CTRL wallet via window.ctrl", () => {
|
|
123
|
+
globalThis.window.ctrl = { ethereum: createMockEthereumProvider() };
|
|
124
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.CTRL);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("detects CTRL wallet via __XDEFI flag", () => {
|
|
128
|
+
globalThis.window.ethereum = createMockEthereumProvider({ __XDEFI: true });
|
|
129
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.CTRL);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("detects Vultisig wallet", () => {
|
|
133
|
+
globalThis.window.vultisig = { ethereum: createMockEthereumProvider() };
|
|
134
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.VULTISIG);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("detects Bitget wallet", () => {
|
|
138
|
+
globalThis.window.bitkeep = { ethereum: createMockEthereumProvider() };
|
|
139
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.BITGET);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("detects OneKey wallet", () => {
|
|
143
|
+
globalThis.window.$onekey = { ethereum: createMockEthereumProvider() };
|
|
144
|
+
expect(listWeb3EVMWallets()).toContain(WalletOption.ONEKEY);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("detects multiple wallets simultaneously", () => {
|
|
148
|
+
globalThis.window.ethereum = createMockEthereumProvider({ isBraveWallet: false });
|
|
149
|
+
globalThis.window.vultisig = { ethereum: createMockEthereumProvider() };
|
|
150
|
+
globalThis.window.bitkeep = { ethereum: createMockEthereumProvider() };
|
|
151
|
+
|
|
152
|
+
const wallets = listWeb3EVMWallets();
|
|
153
|
+
expect(wallets).toContain(WalletOption.METAMASK);
|
|
154
|
+
expect(wallets).toContain(WalletOption.VULTISIG);
|
|
155
|
+
expect(wallets).toContain(WalletOption.BITGET);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("isDetected", () => {
|
|
160
|
+
test("returns true for detected wallet", () => {
|
|
161
|
+
globalThis.window.ethereum = createMockEthereumProvider({ isBraveWallet: true });
|
|
162
|
+
expect(isDetected(WalletOption.BRAVE)).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("returns false for undetected wallet", () => {
|
|
166
|
+
expect(isDetected(WalletOption.METAMASK)).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("getETHDefaultWallet", () => {
|
|
171
|
+
test("returns METAMASK as default when no specific wallet detected", () => {
|
|
172
|
+
globalThis.window.ethereum = createMockEthereumProvider();
|
|
173
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.METAMASK);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("returns TRUSTWALLET_WEB when isTrust is true", () => {
|
|
177
|
+
globalThis.window.ethereum = createMockEthereumProvider({ isTrust: true });
|
|
178
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.TRUSTWALLET_WEB);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("returns BRAVE when isBraveWallet is true", () => {
|
|
182
|
+
globalThis.window.ethereum = createMockEthereumProvider({ isBraveWallet: true });
|
|
183
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.BRAVE);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("returns COINBASE_WEB when coinbase provider is selected", () => {
|
|
187
|
+
globalThis.window.ethereum = createMockEthereumProvider({
|
|
188
|
+
overrideIsMetaMask: true,
|
|
189
|
+
selectedProvider: { isCoinbaseWallet: true },
|
|
190
|
+
});
|
|
191
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.COINBASE_WEB);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("returns CTRL when __XDEFI is true", () => {
|
|
195
|
+
globalThis.window.ethereum = createMockEthereumProvider({ __XDEFI: true });
|
|
196
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.CTRL);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("returns ONEKEY when $onekey.ethereum is present", () => {
|
|
200
|
+
globalThis.window.$onekey = { ethereum: createMockEthereumProvider() };
|
|
201
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.ONEKEY);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("returns METAMASK when window.ethereum is undefined", () => {
|
|
205
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.METAMASK);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("priority: isTrust takes precedence over other flags", () => {
|
|
209
|
+
globalThis.window.ethereum = createMockEthereumProvider({ __XDEFI: true, isBraveWallet: true, isTrust: true });
|
|
210
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.TRUSTWALLET_WEB);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("priority: isBraveWallet takes precedence over __XDEFI", () => {
|
|
214
|
+
globalThis.window.ethereum = createMockEthereumProvider({ __XDEFI: true, isBraveWallet: true });
|
|
215
|
+
expect(getETHDefaultWallet()).toBe(WalletOption.BRAVE);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("okxMobileEnabled", () => {
|
|
220
|
+
test("returns false for desktop browser", () => {
|
|
221
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" };
|
|
222
|
+
expect(okxMobileEnabled()).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("returns false for mobile without OKApp", () => {
|
|
226
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)" };
|
|
227
|
+
expect(okxMobileEnabled()).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("returns true for iOS OKApp", () => {
|
|
231
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0) OKApp" };
|
|
232
|
+
expect(okxMobileEnabled()).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("returns true for iPad OKApp", () => {
|
|
236
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (iPad; CPU OS 14_0) OKApp" };
|
|
237
|
+
expect(okxMobileEnabled()).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("returns true for iPod OKApp", () => {
|
|
241
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (iPod touch; CPU iPhone OS 14_0) OKApp" };
|
|
242
|
+
expect(okxMobileEnabled()).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("returns true for Android OKApp", () => {
|
|
246
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (Linux; Android 10) OKApp" };
|
|
247
|
+
expect(okxMobileEnabled()).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("returns true for XiaoMi OKApp", () => {
|
|
251
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (Linux; XiaoMi) OKApp" };
|
|
252
|
+
expect(okxMobileEnabled()).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("returns true for MiuiBrowser OKApp", () => {
|
|
256
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (Linux; MiuiBrowser) OKApp" };
|
|
257
|
+
expect(okxMobileEnabled()).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("returns false for OKApp on desktop (not mobile)", () => {
|
|
261
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (Windows NT 10.0) OKApp" };
|
|
262
|
+
expect(okxMobileEnabled()).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("case insensitive OKApp detection", () => {
|
|
266
|
+
globalThis.navigator = { userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0) okapp" };
|
|
267
|
+
expect(okxMobileEnabled()).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("providerRequest", () => {
|
|
272
|
+
test("throws error when provider is undefined", () => {
|
|
273
|
+
expect(() => providerRequest({ method: "eth_requestAccounts" })).toThrow(USwapError);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("throws error when provider.send is undefined", () => {
|
|
277
|
+
expect(() => providerRequest({ method: "eth_requestAccounts", provider: {} })).toThrow(USwapError);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("calls provider.send with method and empty params when no params provided", () => {
|
|
281
|
+
const mockSend = mock(() => Promise.resolve("result"));
|
|
282
|
+
const provider = createMockBrowserProvider({ send: mockSend });
|
|
283
|
+
|
|
284
|
+
providerRequest({ method: "eth_requestAccounts", provider });
|
|
285
|
+
|
|
286
|
+
expect(mockSend).toHaveBeenCalledWith("eth_requestAccounts", []);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("calls provider.send with array params", () => {
|
|
290
|
+
const mockSend = mock(() => Promise.resolve("result"));
|
|
291
|
+
const provider = createMockBrowserProvider({ send: mockSend });
|
|
292
|
+
|
|
293
|
+
providerRequest({ method: "wallet_switchEthereumChain", params: [{ chainId: "0x1" }], provider });
|
|
294
|
+
|
|
295
|
+
expect(mockSend).toHaveBeenCalledWith("wallet_switchEthereumChain", [{ chainId: "0x1" }]);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("wraps non-array params in array", () => {
|
|
299
|
+
const mockSend = mock(() => Promise.resolve("result"));
|
|
300
|
+
const provider = createMockBrowserProvider({ send: mockSend });
|
|
301
|
+
|
|
302
|
+
providerRequest({ method: "wallet_addEthereumChain", params: { chainId: "0x1" }, provider });
|
|
303
|
+
|
|
304
|
+
expect(mockSend).toHaveBeenCalledWith("wallet_addEthereumChain", [{ chainId: "0x1" }]);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("addEVMWalletNetwork", () => {
|
|
309
|
+
test("calls providerRequest with wallet_addEthereumChain", () => {
|
|
310
|
+
const mockSend = mock(() => Promise.resolve());
|
|
311
|
+
const provider = createMockBrowserProvider({ send: mockSend });
|
|
312
|
+
const networkParams = {
|
|
313
|
+
chainId: "0x89",
|
|
314
|
+
chainName: "Polygon",
|
|
315
|
+
nativeCurrency: { decimals: 18, name: "MATIC", symbol: "MATIC" },
|
|
316
|
+
rpcUrls: ["https://polygon-rpc.com"],
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
addEVMWalletNetwork(provider, networkParams);
|
|
320
|
+
|
|
321
|
+
expect(mockSend).toHaveBeenCalledWith("wallet_addEthereumChain", [networkParams]);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe("switchEVMWalletNetwork", () => {
|
|
326
|
+
test("calls wallet_switchEthereumChain with chain config", async () => {
|
|
327
|
+
const mockSend = mock(() => Promise.resolve());
|
|
328
|
+
const provider = createMockBrowserProvider({ send: mockSend });
|
|
329
|
+
|
|
330
|
+
await switchEVMWalletNetwork(provider, Chain.Ethereum);
|
|
331
|
+
|
|
332
|
+
expect(mockSend).toHaveBeenCalledWith("wallet_switchEthereumChain", [{ chainId: "0x1" }]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("adds network when switch fails and networkParams provided", async () => {
|
|
336
|
+
const mockSend = mock()
|
|
337
|
+
.mockImplementationOnce(() => Promise.reject(new Error("Chain not found")))
|
|
338
|
+
.mockImplementationOnce(() => Promise.resolve());
|
|
339
|
+
|
|
340
|
+
const provider = createMockBrowserProvider({ send: mockSend });
|
|
341
|
+
const networkParams = {
|
|
342
|
+
chainId: "0x89",
|
|
343
|
+
chainName: "Polygon",
|
|
344
|
+
nativeCurrency: { decimals: 18, name: "MATIC", symbol: "MATIC" },
|
|
345
|
+
rpcUrls: ["https://polygon-rpc.com"],
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
await switchEVMWalletNetwork(provider, Chain.Polygon, networkParams);
|
|
349
|
+
|
|
350
|
+
expect(mockSend).toHaveBeenCalledTimes(2);
|
|
351
|
+
expect(mockSend).toHaveBeenLastCalledWith("wallet_addEthereumChain", [networkParams]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("throws error when switch fails and no networkParams provided", async () => {
|
|
355
|
+
const mockSend = mock(() => Promise.reject(new Error("Chain not found")));
|
|
356
|
+
const provider = createMockBrowserProvider({ send: mockSend });
|
|
357
|
+
|
|
358
|
+
await expect(switchEVMWalletNetwork(provider, Chain.Ethereum)).rejects.toThrow(USwapError);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe("filterSupportedChains", () => {
|
|
363
|
+
test("returns supported chains only", () => {
|
|
364
|
+
const result = filterSupportedChains({
|
|
365
|
+
chains: [Chain.Ethereum, Chain.Bitcoin, Chain.Avalanche],
|
|
366
|
+
supportedChains: [Chain.Ethereum, Chain.Avalanche],
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
expect(result).toEqual([Chain.Ethereum, Chain.Avalanche]);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("throws error when no chains are supported", () => {
|
|
373
|
+
expect(() =>
|
|
374
|
+
filterSupportedChains({
|
|
375
|
+
chains: [Chain.Bitcoin],
|
|
376
|
+
supportedChains: [Chain.Ethereum],
|
|
377
|
+
walletType: WalletOption.METAMASK,
|
|
378
|
+
}),
|
|
379
|
+
).toThrow(USwapError);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("handles empty chains array", () => {
|
|
383
|
+
expect(() => filterSupportedChains({ chains: [], supportedChains: [Chain.Ethereum] })).toThrow(USwapError);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("warns about unsupported chains but returns supported ones", () => {
|
|
387
|
+
const result = filterSupportedChains({
|
|
388
|
+
chains: [Chain.Ethereum, Chain.Bitcoin],
|
|
389
|
+
supportedChains: [Chain.Ethereum],
|
|
390
|
+
walletType: WalletOption.METAMASK,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(result).toEqual([Chain.Ethereum]);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe("wrapMethodWithNetworkSwitch", () => {
|
|
398
|
+
test("calls original function directly when network already matches (no switch needed)", async () => {
|
|
399
|
+
const originalFunc = mock(() => Promise.resolve("result"));
|
|
400
|
+
const mockSend = mock(() => Promise.resolve());
|
|
401
|
+
// Mock chainId that when toString() equals chainIdHex "0x1"
|
|
402
|
+
// This simulates the API returning hex format
|
|
403
|
+
const mockChainId = { toString: () => "0x1" };
|
|
404
|
+
const provider = createMockBrowserProvider({
|
|
405
|
+
getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })),
|
|
406
|
+
send: mockSend,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const wrapped = wrapMethodWithNetworkSwitch(originalFunc, provider, Chain.Ethereum);
|
|
410
|
+
const result = await wrapped("arg1", "arg2");
|
|
411
|
+
|
|
412
|
+
expect(mockSend).not.toHaveBeenCalled(); // Should NOT switch network
|
|
413
|
+
expect(originalFunc).toHaveBeenCalledWith("arg1", "arg2");
|
|
414
|
+
expect(result).toBe("result");
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("switches network before calling function when network differs", async () => {
|
|
418
|
+
const originalFunc = mock(() => Promise.resolve("result"));
|
|
419
|
+
const mockSend = mock(() => Promise.resolve());
|
|
420
|
+
// Mock chainId for Polygon (0x89 in hex)
|
|
421
|
+
const mockChainId = { toString: () => "0x89" };
|
|
422
|
+
const provider = createMockBrowserProvider({
|
|
423
|
+
getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })),
|
|
424
|
+
send: mockSend,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const wrapped = wrapMethodWithNetworkSwitch(originalFunc, provider, Chain.Ethereum);
|
|
428
|
+
await wrapped();
|
|
429
|
+
|
|
430
|
+
expect(mockSend).toHaveBeenCalledWith("wallet_switchEthereumChain", [{ chainId: "0x1" }]);
|
|
431
|
+
expect(originalFunc).toHaveBeenCalled();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("throws USwapError when network switch fails", async () => {
|
|
435
|
+
const originalFunc = mock(() => Promise.resolve("result"));
|
|
436
|
+
const mockChainId = { toString: () => "0x89" }; // Different from Ethereum
|
|
437
|
+
const provider = createMockBrowserProvider({
|
|
438
|
+
getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })),
|
|
439
|
+
send: mock(() => Promise.reject(new Error("User rejected"))),
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const wrapped = wrapMethodWithNetworkSwitch(originalFunc, provider, Chain.Ethereum);
|
|
443
|
+
|
|
444
|
+
await expect(wrapped()).rejects.toThrow(USwapError);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("preserves function return value after network switch", async () => {
|
|
448
|
+
const originalFunc = mock(() => Promise.resolve({ txHash: "0xabc123" }));
|
|
449
|
+
const mockChainId = { toString: () => "0x38" }; // BSC hex
|
|
450
|
+
const provider = createMockBrowserProvider({
|
|
451
|
+
getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })),
|
|
452
|
+
send: mock(() => Promise.resolve()),
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const wrapped = wrapMethodWithNetworkSwitch(originalFunc, provider, Chain.Ethereum);
|
|
456
|
+
const result = await wrapped();
|
|
457
|
+
|
|
458
|
+
expect(result).toEqual({ txHash: "0xabc123" });
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe("prepareNetworkSwitch", () => {
|
|
463
|
+
test("wraps standard EVM methods", () => {
|
|
464
|
+
const mockSend = mock(() => Promise.resolve());
|
|
465
|
+
const mockChainId = { toString: () => "0x1" };
|
|
466
|
+
const provider = createMockBrowserProvider({
|
|
467
|
+
getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })),
|
|
468
|
+
send: mockSend,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const toolbox = {
|
|
472
|
+
approve: mock(() => Promise.resolve()),
|
|
473
|
+
nonWrappedMethod: mock(() => Promise.resolve()),
|
|
474
|
+
sendTransaction: mock(() => Promise.resolve()),
|
|
475
|
+
transfer: mock(() => Promise.resolve()),
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const result = prepareNetworkSwitch({ chain: Chain.Ethereum, provider, toolbox });
|
|
479
|
+
|
|
480
|
+
// Wrapped methods should be different from original
|
|
481
|
+
expect(result.transfer).not.toBe(toolbox.transfer);
|
|
482
|
+
expect(result.approve).not.toBe(toolbox.approve);
|
|
483
|
+
expect(result.sendTransaction).not.toBe(toolbox.sendTransaction);
|
|
484
|
+
// Non-standard methods should remain unchanged
|
|
485
|
+
expect(result.nonWrappedMethod).toBe(toolbox.nonWrappedMethod);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("wraps custom method names", () => {
|
|
489
|
+
const mockChainId = { toString: () => "0x1" };
|
|
490
|
+
const provider = createMockBrowserProvider({ getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })) });
|
|
491
|
+
|
|
492
|
+
const toolbox = { anotherMethod: mock(() => Promise.resolve()), customMethod: mock(() => Promise.resolve()) };
|
|
493
|
+
|
|
494
|
+
const result = prepareNetworkSwitch({ chain: Chain.Ethereum, methodNames: ["customMethod"], provider, toolbox });
|
|
495
|
+
|
|
496
|
+
expect(result.customMethod).not.toBe(toolbox.customMethod);
|
|
497
|
+
expect(result.anotherMethod).toBe(toolbox.anotherMethod);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("skips non-function properties", () => {
|
|
501
|
+
const mockChainId = { toString: () => "0x1" };
|
|
502
|
+
const provider = createMockBrowserProvider({ getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })) });
|
|
503
|
+
|
|
504
|
+
const toolbox = { someNumber: 42, someValue: "not a function", transfer: mock(() => Promise.resolve()) };
|
|
505
|
+
|
|
506
|
+
const result = prepareNetworkSwitch({ chain: Chain.Ethereum, provider, toolbox });
|
|
507
|
+
|
|
508
|
+
expect(result.someValue).toBe("not a function");
|
|
509
|
+
expect(result.someNumber).toBe(42);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test("skips undefined methods in toolbox", () => {
|
|
513
|
+
const mockChainId = { toString: () => "0x1" };
|
|
514
|
+
const provider = createMockBrowserProvider({ getNetwork: mock(() => Promise.resolve({ chainId: mockChainId })) });
|
|
515
|
+
|
|
516
|
+
const toolbox = {
|
|
517
|
+
transfer: mock(() => Promise.resolve()),
|
|
518
|
+
// approve is not defined
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const result = prepareNetworkSwitch({ chain: Chain.Ethereum, provider, toolbox });
|
|
522
|
+
|
|
523
|
+
expect(result.transfer).toBeDefined();
|
|
524
|
+
expect(result.approve).toBeUndefined();
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe("addAccountsChangedCallback", () => {
|
|
529
|
+
test("registers callback on window.ethereum", () => {
|
|
530
|
+
const mockOn = mock(() => {});
|
|
531
|
+
globalThis.window.ethereum = createMockEthereumProvider({ on: mockOn });
|
|
532
|
+
|
|
533
|
+
const callback = mock(() => {});
|
|
534
|
+
addAccountsChangedCallback(callback);
|
|
535
|
+
|
|
536
|
+
expect(mockOn).toHaveBeenCalledWith("accountsChanged", expect.any(Function));
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test("executes callback when ethereum accounts change", () => {
|
|
540
|
+
let capturedHandler: (() => void) | undefined;
|
|
541
|
+
const mockOn = mock((event: string, handler: () => void) => {
|
|
542
|
+
if (event === "accountsChanged") capturedHandler = handler;
|
|
543
|
+
});
|
|
544
|
+
globalThis.window.ethereum = createMockEthereumProvider({ on: mockOn });
|
|
545
|
+
|
|
546
|
+
const callback = mock(() => {});
|
|
547
|
+
addAccountsChangedCallback(callback);
|
|
548
|
+
|
|
549
|
+
// Simulate account change event
|
|
550
|
+
capturedHandler?.();
|
|
551
|
+
|
|
552
|
+
expect(callback).toHaveBeenCalled();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test("registers callback on window.ctrl.ethereum", () => {
|
|
556
|
+
const mockOnEthereum = mock(() => {});
|
|
557
|
+
const mockOnCtrl = mock(() => {});
|
|
558
|
+
globalThis.window.ethereum = createMockEthereumProvider({ on: mockOnEthereum });
|
|
559
|
+
globalThis.window.ctrl = { ethereum: createMockEthereumProvider({ on: mockOnCtrl }) };
|
|
560
|
+
|
|
561
|
+
const callback = mock(() => {});
|
|
562
|
+
addAccountsChangedCallback(callback);
|
|
563
|
+
|
|
564
|
+
expect(mockOnEthereum).toHaveBeenCalledWith("accountsChanged", expect.any(Function));
|
|
565
|
+
expect(mockOnCtrl).toHaveBeenCalledWith("accountsChanged", expect.any(Function));
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("executes callback when ctrl accounts change", () => {
|
|
569
|
+
let capturedHandler: (() => void) | undefined;
|
|
570
|
+
globalThis.window.ethereum = createMockEthereumProvider({ on: mock(() => {}) });
|
|
571
|
+
globalThis.window.ctrl = {
|
|
572
|
+
ethereum: createMockEthereumProvider({
|
|
573
|
+
on: mock((event: string, handler: () => void) => {
|
|
574
|
+
if (event === "accountsChanged") capturedHandler = handler;
|
|
575
|
+
}),
|
|
576
|
+
}),
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const callback = mock(() => {});
|
|
580
|
+
addAccountsChangedCallback(callback);
|
|
581
|
+
|
|
582
|
+
// Simulate account change event from ctrl
|
|
583
|
+
capturedHandler?.();
|
|
584
|
+
|
|
585
|
+
expect(callback).toHaveBeenCalled();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test("handles missing ethereum providers gracefully", () => {
|
|
589
|
+
globalThis.window = { ...createMockWindow(), ctrl: undefined, ethereum: undefined };
|
|
590
|
+
|
|
591
|
+
const callback = mock(() => {});
|
|
592
|
+
// Should not throw when providers are undefined
|
|
593
|
+
expect(() => addAccountsChangedCallback(callback)).not.toThrow();
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
describe("getEIP6963Wallets", () => {
|
|
598
|
+
test("sets up event listener and dispatches request", () => {
|
|
599
|
+
const mockAddEventListener = mock(() => {});
|
|
600
|
+
const mockDispatchEvent = mock(() => true);
|
|
601
|
+
globalThis.window = {
|
|
602
|
+
...createMockWindow(),
|
|
603
|
+
addEventListener: mockAddEventListener,
|
|
604
|
+
dispatchEvent: mockDispatchEvent,
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
const { providers, removeEIP6963EventListener } = getEIP6963Wallets();
|
|
608
|
+
|
|
609
|
+
expect(mockAddEventListener).toHaveBeenCalledWith("eip6963:announceProvider", expect.any(Function));
|
|
610
|
+
expect(mockDispatchEvent).toHaveBeenCalled();
|
|
611
|
+
expect(providers).toEqual([]);
|
|
612
|
+
expect(typeof removeEIP6963EventListener).toBe("function");
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test("removeEIP6963EventListener removes the event listener", () => {
|
|
616
|
+
const mockRemoveEventListener = mock(() => {});
|
|
617
|
+
globalThis.window = { ...createMockWindow(), removeEventListener: mockRemoveEventListener };
|
|
618
|
+
|
|
619
|
+
const { removeEIP6963EventListener } = getEIP6963Wallets();
|
|
620
|
+
removeEIP6963EventListener();
|
|
621
|
+
|
|
622
|
+
expect(mockRemoveEventListener).toHaveBeenCalledWith("eip6963:announceProvider", expect.any(Function));
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
});
|