@tomo-inc/chains-service 0.0.23 → 0.0.25
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/index.cjs +42 -24
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +43 -25
- package/package.json +3 -2
- package/project.json +1 -1
- package/src/__tests__/config.test.ts +46 -0
- package/src/__tests__/dogecoin-utils.test.ts +147 -0
- package/src/__tests__/evm-utils.test.ts +133 -0
- package/src/__tests__/index.test.ts +40 -0
- package/src/__tests__/services.test.ts +285 -0
- package/src/__tests__/solana-utils.test.ts +131 -0
- package/src/__tests__/utils.test.ts +52 -0
- package/src/__tests__/wallet.test.ts +350 -0
- package/src/api/__tests__/base.test.ts +146 -0
- package/src/api/__tests__/index.test.ts +51 -0
- package/src/api/__tests__/network.test.ts +153 -0
- package/src/api/__tests__/token.test.ts +231 -2
- package/src/api/__tests__/transaction.test.ts +121 -6
- package/src/api/__tests__/user.test.ts +237 -3
- package/src/api/__tests__/wallet.test.ts +174 -4
- package/src/api/network.ts +9 -1
- package/src/api/utils/__tests__/index.test.ts +91 -0
- package/src/api/utils/__tests__/signature.test.ts +124 -0
- package/src/api/utils/index.ts +6 -2
- package/src/base/__tests__/network.test.ts +119 -0
- package/src/base/__tests__/service.test.ts +68 -0
- package/src/base/__tests__/token.test.ts +123 -0
- package/src/base/__tests__/transaction.test.ts +210 -0
- package/src/config.ts +2 -1
- package/src/dogecoin/__tests__/base.test.ts +76 -0
- package/src/dogecoin/__tests__/rpc.test.ts +465 -0
- package/src/dogecoin/__tests__/service-extended.test.ts +420 -0
- package/src/dogecoin/__tests__/utils-doge.test.ts +244 -0
- package/src/dogecoin/__tests__/utils-extended.test.ts +323 -0
- package/src/dogecoin/base.ts +1 -0
- package/src/dogecoin/config.ts +2 -2
- package/src/dogecoin/rpc.ts +10 -1
- package/src/dogecoin/service.ts +9 -5
- package/src/evm/__tests__/rpc.test.ts +132 -0
- package/src/evm/__tests__/service.test.ts +535 -0
- package/src/evm/__tests__/utils.test.ts +170 -0
- package/src/evm/service.ts +11 -0
- package/src/evm/utils.ts +2 -2
- package/src/solana/__tests__/service.test.ts +425 -0
- package/src/solana/__tests__/utils.test.ts +937 -0
- package/src/solana/config.ts +1 -1
- package/src/solana/service.ts +2 -0
- package/src/solana/utils.ts +2 -16
- package/src/utils/index.ts +1 -1
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { EvmService } from "../service";
|
|
3
|
+
import { ChainTypeEnum } from "@tomo-inc/wallet-utils";
|
|
4
|
+
import { IAccountInfo, TomoAppInfo } from "../../types";
|
|
5
|
+
|
|
6
|
+
// Mock viem
|
|
7
|
+
vi.mock("viem", async () => {
|
|
8
|
+
const actual = await vi.importActual("viem");
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
createPublicClient: vi.fn(),
|
|
12
|
+
http: vi.fn(),
|
|
13
|
+
parseTransaction: vi.fn(),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Mock rpc module
|
|
18
|
+
vi.mock("../rpc", () => ({
|
|
19
|
+
getRPCClient: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe("EvmService", () => {
|
|
23
|
+
let mockAccountInfo: IAccountInfo;
|
|
24
|
+
let mockTomoAppInfo: TomoAppInfo;
|
|
25
|
+
let service: EvmService;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
(EvmService as any).instance = undefined;
|
|
30
|
+
|
|
31
|
+
mockAccountInfo = {
|
|
32
|
+
getCurrent: vi.fn().mockResolvedValue([
|
|
33
|
+
{
|
|
34
|
+
address: "0x1234567890123456789012345678901234567890",
|
|
35
|
+
publicKey: "test-public-key",
|
|
36
|
+
},
|
|
37
|
+
]),
|
|
38
|
+
signMessage: vi.fn().mockResolvedValue("0xsignature"),
|
|
39
|
+
signTypedData: vi.fn().mockResolvedValue("0xtypedSignature"),
|
|
40
|
+
signTransaction: vi.fn().mockResolvedValue("0xsignedTx"),
|
|
41
|
+
} as any;
|
|
42
|
+
|
|
43
|
+
mockTomoAppInfo = {
|
|
44
|
+
tomoStage: "dev",
|
|
45
|
+
appId: "test-app",
|
|
46
|
+
appName: "Test App",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("eth_chainId", () => {
|
|
53
|
+
it("should get chain ID", async () => {
|
|
54
|
+
const mockChain = {
|
|
55
|
+
chainId: 1,
|
|
56
|
+
rpcUrls: ["https://rpc.example.com"],
|
|
57
|
+
};
|
|
58
|
+
vi.spyOn(service as any, "getCurrentChain").mockResolvedValue(mockChain);
|
|
59
|
+
|
|
60
|
+
const chainId = await service.eth_chainId();
|
|
61
|
+
expect(chainId).toBe("0x1");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("getCurrentChain", () => {
|
|
66
|
+
it("should return current network via getCurrentNetwork", async () => {
|
|
67
|
+
const mockNetwork = { chainId: 1, rpcUrls: ["https://rpc.example.com"] };
|
|
68
|
+
(service as any).networks = {
|
|
69
|
+
setChainType: vi.fn(),
|
|
70
|
+
getCurrentNetwork: vi.fn().mockResolvedValue(mockNetwork),
|
|
71
|
+
};
|
|
72
|
+
const result = await (service as any).getCurrentChain();
|
|
73
|
+
expect(result).toEqual(mockNetwork);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("wallet_switchEthereumChain", () => {
|
|
78
|
+
it("should switch chain successfully", async () => {
|
|
79
|
+
const mockChain = {
|
|
80
|
+
chainId: 5,
|
|
81
|
+
platformType: "EVM",
|
|
82
|
+
};
|
|
83
|
+
vi.spyOn(service as any, "isChainSupported").mockResolvedValue(true);
|
|
84
|
+
(service as any).networks = {
|
|
85
|
+
setCurrentNetwork: vi.fn().mockResolvedValue(true),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const result = await service.wallet_switchEthereumChain([{ chainId: "0x5" }]);
|
|
89
|
+
expect(result).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should throw error when chain is not supported", async () => {
|
|
93
|
+
vi.spyOn(service as any, "isChainSupported").mockResolvedValue(false);
|
|
94
|
+
|
|
95
|
+
await expect(service.wallet_switchEthereumChain([{ chainId: "0x999" }])).rejects.toThrow(
|
|
96
|
+
"Chain 0x999 is not supported",
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("isChainSupported", () => {
|
|
102
|
+
it("should return false when getNetworkByChainId returns null", async () => {
|
|
103
|
+
(service as any).networks = {
|
|
104
|
+
getNetworkByChainId: vi.fn().mockResolvedValue(null),
|
|
105
|
+
};
|
|
106
|
+
const result = await (service as any).isChainSupported("0x1");
|
|
107
|
+
expect(result).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should return false when chainInfo is not EVM", async () => {
|
|
111
|
+
(service as any).networks = {
|
|
112
|
+
getNetworkByChainId: vi.fn().mockResolvedValue({ platformType: "SOLANA" }),
|
|
113
|
+
};
|
|
114
|
+
const result = await (service as any).isChainSupported("0x1");
|
|
115
|
+
expect(result).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should return false when res.chainId is empty (covers line 90-91)", async () => {
|
|
119
|
+
(service as any).networks = { getNetworkByChainId: vi.fn() };
|
|
120
|
+
const result = await (service as any).isChainSupported("");
|
|
121
|
+
expect(result).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("eth_getBalance", () => {
|
|
126
|
+
it("should return balance", async () => {
|
|
127
|
+
const mockRpcClient = { getBalance: vi.fn().mockResolvedValue(BigInt(1000000000000000000)) };
|
|
128
|
+
vi.spyOn(service as any, "getCurrentChain").mockResolvedValue({ rpcUrls: ["https://rpc.example.com"] });
|
|
129
|
+
const { getRPCClient } = await import("../rpc");
|
|
130
|
+
vi.mocked(getRPCClient).mockReturnValue({ rpcClient: mockRpcClient } as any);
|
|
131
|
+
|
|
132
|
+
const balance = await service.eth_getBalance(["0x1234567890123456789012345678901234567890", "latest"]);
|
|
133
|
+
expect(balance).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should return 0 when getBalance throws", async () => {
|
|
137
|
+
const mockRpcClient = { getBalance: vi.fn().mockRejectedValue(new Error("RPC error")) };
|
|
138
|
+
vi.spyOn(service as any, "getCurrentChain").mockResolvedValue({ rpcUrls: ["https://rpc.example.com"] });
|
|
139
|
+
const { getRPCClient } = await import("../rpc");
|
|
140
|
+
vi.mocked(getRPCClient).mockReturnValue({ rpcClient: mockRpcClient } as any);
|
|
141
|
+
|
|
142
|
+
const balance = await service.eth_getBalance(["0x1234567890123456789012345678901234567890", "latest"]);
|
|
143
|
+
expect(balance).toBe("0");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should throw when type is not latest", async () => {
|
|
147
|
+
await expect(
|
|
148
|
+
service.eth_getBalance(["0x1234567890123456789012345678901234567890", "pending" as any]),
|
|
149
|
+
).rejects.toThrow("type is not supported");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("eth_estimateGas", () => {
|
|
154
|
+
it("should estimate gas successfully", async () => {
|
|
155
|
+
const mockRpcClient = {
|
|
156
|
+
estimateGas: vi.fn().mockResolvedValue("0x5208"),
|
|
157
|
+
};
|
|
158
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
159
|
+
|
|
160
|
+
const gas = await service.eth_estimateGas([
|
|
161
|
+
{
|
|
162
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
163
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
164
|
+
value: "0x1",
|
|
165
|
+
chainId: "0x1",
|
|
166
|
+
},
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
expect(gas).toBe("0x5208");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should throw error when address mismatch", async () => {
|
|
173
|
+
await expect(
|
|
174
|
+
service.eth_estimateGas([
|
|
175
|
+
{
|
|
176
|
+
from: "0x1111111111111111111111111111111111111111",
|
|
177
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
178
|
+
value: "0x1",
|
|
179
|
+
chainId: "0x1",
|
|
180
|
+
},
|
|
181
|
+
]),
|
|
182
|
+
).rejects.toThrow("address is not the current account");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should throw error when to is not set", async () => {
|
|
186
|
+
await expect(
|
|
187
|
+
service.eth_estimateGas([
|
|
188
|
+
{
|
|
189
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
190
|
+
to: undefined as any,
|
|
191
|
+
value: "0x1",
|
|
192
|
+
chainId: "0x1",
|
|
193
|
+
},
|
|
194
|
+
]),
|
|
195
|
+
).rejects.toThrow("to is not set");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should return default gas on error", async () => {
|
|
199
|
+
const mockRpcClient = {
|
|
200
|
+
estimateGas: vi.fn().mockRejectedValue(new Error("RPC Error")),
|
|
201
|
+
};
|
|
202
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
203
|
+
|
|
204
|
+
const gas = await service.eth_estimateGas([
|
|
205
|
+
{
|
|
206
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
207
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
208
|
+
value: "0x1",
|
|
209
|
+
chainId: "0x1",
|
|
210
|
+
},
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
expect(gas).toBeDefined();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("eth_getTransactionCount", () => {
|
|
218
|
+
it("should get transaction count", async () => {
|
|
219
|
+
const mockRpcClient = {
|
|
220
|
+
getTransactionCount: vi.fn().mockResolvedValue(5),
|
|
221
|
+
};
|
|
222
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
223
|
+
|
|
224
|
+
const nonce = await service.eth_getTransactionCount(["0x1234567890123456789012345678901234567890", "latest"]);
|
|
225
|
+
expect(nonce).toBe(5);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should accept pending type", async () => {
|
|
229
|
+
const mockRpcClient = {
|
|
230
|
+
getTransactionCount: vi.fn().mockResolvedValue(3),
|
|
231
|
+
};
|
|
232
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
233
|
+
|
|
234
|
+
const nonce = await service.eth_getTransactionCount(["0x1234567890123456789012345678901234567890", "pending"]);
|
|
235
|
+
expect(nonce).toBe(3);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should throw error when address mismatch", async () => {
|
|
239
|
+
await expect(
|
|
240
|
+
service.eth_getTransactionCount(["0x1111111111111111111111111111111111111111", "latest"]),
|
|
241
|
+
).rejects.toThrow("address is not the current account");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should throw error for unsupported type", async () => {
|
|
245
|
+
await expect(
|
|
246
|
+
service.eth_getTransactionCount(["0x1234567890123456789012345678901234567890", "earliest" as any]),
|
|
247
|
+
).rejects.toThrow("type is not supported");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should throw when createPublicClient getTransactionCount fails", async () => {
|
|
251
|
+
const mockRpcClient = {
|
|
252
|
+
getTransactionCount: vi.fn().mockRejectedValue(new Error("RPC failed")),
|
|
253
|
+
};
|
|
254
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
255
|
+
|
|
256
|
+
await expect(
|
|
257
|
+
service.eth_getTransactionCount(["0x1234567890123456789012345678901234567890", "latest"]),
|
|
258
|
+
).rejects.toThrow("Failed to get nonce");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe("eth_signTransaction", () => {
|
|
263
|
+
it("should sign transaction successfully", async () => {
|
|
264
|
+
const mockTx = {
|
|
265
|
+
hash: "0xtxhash",
|
|
266
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
267
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
268
|
+
value: BigInt(1000),
|
|
269
|
+
gas: BigInt(21000),
|
|
270
|
+
nonce: 0,
|
|
271
|
+
chainId: 1,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const { parseTransaction } = await import("viem");
|
|
275
|
+
vi.mocked(parseTransaction as any).mockReturnValue(mockTx);
|
|
276
|
+
|
|
277
|
+
const tx = await service.eth_signTransaction([
|
|
278
|
+
{
|
|
279
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
280
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
281
|
+
value: "0x3e8",
|
|
282
|
+
chainId: "0x1",
|
|
283
|
+
gas: "0x5208",
|
|
284
|
+
nonce: "0x0",
|
|
285
|
+
},
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
expect(tx).toBeDefined();
|
|
289
|
+
expect(mockAccountInfo.signTransaction).toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should throw error when gas is not set", async () => {
|
|
293
|
+
await expect(
|
|
294
|
+
service.eth_signTransaction([
|
|
295
|
+
{
|
|
296
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
297
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
298
|
+
value: "0x3e8",
|
|
299
|
+
chainId: "0x1",
|
|
300
|
+
},
|
|
301
|
+
]),
|
|
302
|
+
).rejects.toThrow("gas or to or chainId is not set");
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should throw error when address mismatch", async () => {
|
|
306
|
+
await expect(
|
|
307
|
+
service.eth_signTransaction([
|
|
308
|
+
{
|
|
309
|
+
from: "0x1111111111111111111111111111111111111111",
|
|
310
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
311
|
+
value: "0x3e8",
|
|
312
|
+
chainId: "0x1",
|
|
313
|
+
gas: "0x5208",
|
|
314
|
+
nonce: "0x0",
|
|
315
|
+
},
|
|
316
|
+
]),
|
|
317
|
+
).rejects.toThrow("from is not the current account");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should throw when serializedTransaction is empty", async () => {
|
|
321
|
+
mockAccountInfo.signTransaction = vi.fn().mockResolvedValue("");
|
|
322
|
+
|
|
323
|
+
await expect(
|
|
324
|
+
service.eth_signTransaction([
|
|
325
|
+
{
|
|
326
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
327
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
328
|
+
value: "0x3e8",
|
|
329
|
+
chainId: "0x1",
|
|
330
|
+
gas: "0x5208",
|
|
331
|
+
nonce: "0x0",
|
|
332
|
+
},
|
|
333
|
+
]),
|
|
334
|
+
).rejects.toThrow("eth_signTransaction error");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should throw on sign error", async () => {
|
|
338
|
+
mockAccountInfo.signTransaction = vi.fn().mockRejectedValue(new Error("sign failed"));
|
|
339
|
+
|
|
340
|
+
await expect(
|
|
341
|
+
service.eth_signTransaction([
|
|
342
|
+
{
|
|
343
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
344
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
345
|
+
value: "0x3e8",
|
|
346
|
+
chainId: "0x1",
|
|
347
|
+
gas: "0x5208",
|
|
348
|
+
nonce: "0x0",
|
|
349
|
+
},
|
|
350
|
+
]),
|
|
351
|
+
).rejects.toThrow("eth_signTransaction error");
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe("eth_sendRawTransaction", () => {
|
|
356
|
+
it("should send raw transaction", async () => {
|
|
357
|
+
const mockRpcClient = {
|
|
358
|
+
sendRawTransaction: vi.fn().mockResolvedValue("0xtxhash"),
|
|
359
|
+
};
|
|
360
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
361
|
+
|
|
362
|
+
const hash = await service.eth_sendRawTransaction(["0xsignedTx"]);
|
|
363
|
+
expect(hash).toBe("0xtxhash");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("should send raw transaction with custom rpcUrl", async () => {
|
|
367
|
+
const mockRpcClient = {
|
|
368
|
+
sendRawTransaction: vi.fn().mockResolvedValue("0xtxhash"),
|
|
369
|
+
};
|
|
370
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
371
|
+
|
|
372
|
+
const hash = await service.eth_sendRawTransaction(["0xsignedTx"], "https://rpc.example.com" as any);
|
|
373
|
+
expect(hash).toBe("0xtxhash");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("should throw when sendRawTransaction fails", async () => {
|
|
377
|
+
const mockRpcClient = {
|
|
378
|
+
sendRawTransaction: vi.fn().mockRejectedValue(new Error("reverted")),
|
|
379
|
+
};
|
|
380
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
381
|
+
|
|
382
|
+
await expect(service.eth_sendRawTransaction(["0xsignedTx"])).rejects.toThrow("reverted");
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe("getTransaction", () => {
|
|
387
|
+
it("should get transaction", async () => {
|
|
388
|
+
const mockTx = {
|
|
389
|
+
hash: "0xtxhash",
|
|
390
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
391
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
392
|
+
value: BigInt(1000),
|
|
393
|
+
};
|
|
394
|
+
const mockRpcClient = {
|
|
395
|
+
getTransaction: vi.fn().mockResolvedValue(mockTx),
|
|
396
|
+
};
|
|
397
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
398
|
+
|
|
399
|
+
const tx = await service.getTransaction("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234");
|
|
400
|
+
expect(tx).toEqual(mockTx);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("should throw error for invalid hash", async () => {
|
|
404
|
+
await expect(service.getTransaction("invalid" as any)).rejects.toThrow("txId is not valid");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("should throw when getTransaction RPC fails", async () => {
|
|
408
|
+
const mockRpcClient = {
|
|
409
|
+
getTransaction: vi.fn().mockRejectedValue(new Error("tx not found")),
|
|
410
|
+
};
|
|
411
|
+
vi.spyOn(service as any, "createPublicClient").mockResolvedValue(mockRpcClient);
|
|
412
|
+
|
|
413
|
+
await expect(
|
|
414
|
+
service.getTransaction("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234"),
|
|
415
|
+
).rejects.toThrow("tx not found");
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe("createPublicClient", () => {
|
|
420
|
+
it("should use rpcUrl when provided", async () => {
|
|
421
|
+
const { createPublicClient, http } = await import("viem");
|
|
422
|
+
const client = await (service as any).createPublicClient({ rpcUrl: "https://custom.rpc.com" });
|
|
423
|
+
expect(createPublicClient).toHaveBeenCalled();
|
|
424
|
+
expect(http).toHaveBeenCalledWith("https://custom.rpc.com");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("should use chainId to get rpcUrl when rpcUrl not provided", async () => {
|
|
428
|
+
(service as any).networks = {
|
|
429
|
+
setChainType: vi.fn(),
|
|
430
|
+
getNetworkByChainId: vi.fn().mockResolvedValue({ rpcUrls: ["https://chain.rpc.com"] }),
|
|
431
|
+
};
|
|
432
|
+
const { createPublicClient, http } = await import("viem");
|
|
433
|
+
await (service as any).createPublicClient({ chainId: "1" });
|
|
434
|
+
expect(http).toHaveBeenCalledWith("https://chain.rpc.com");
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("should use getCurrentChain when neither rpcUrl nor chainId provided", async () => {
|
|
438
|
+
vi.spyOn(service as any, "getCurrentChain").mockResolvedValue({ rpcUrls: ["https://current.rpc.com"] });
|
|
439
|
+
const { http } = await import("viem");
|
|
440
|
+
await (service as any).createPublicClient({});
|
|
441
|
+
expect(http).toHaveBeenCalledWith("https://current.rpc.com");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("should throw when rpcUrl is not set", async () => {
|
|
445
|
+
vi.spyOn(service as any, "getCurrentChain").mockResolvedValue({ rpcUrls: [] });
|
|
446
|
+
await expect((service as any).createPublicClient({})).rejects.toThrow("rpcUrl is not set");
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe("_queryGasInfo", () => {
|
|
451
|
+
it("should query gas info successfully", async () => {
|
|
452
|
+
const mockGasInfo = {
|
|
453
|
+
baseFee: 1000000000,
|
|
454
|
+
gasLimit: 21000,
|
|
455
|
+
priorityFeeLow: 1000000000,
|
|
456
|
+
priorityFeeMedium: 1500000000,
|
|
457
|
+
priorityFeeHigh: 2000000000,
|
|
458
|
+
};
|
|
459
|
+
(service as any).transactions = {
|
|
460
|
+
queryGasInfo: vi.fn().mockResolvedValue({
|
|
461
|
+
success: true,
|
|
462
|
+
data: mockGasInfo,
|
|
463
|
+
}),
|
|
464
|
+
};
|
|
465
|
+
(service as any).networks = {
|
|
466
|
+
getNetworkByChainId: vi.fn().mockResolvedValue({
|
|
467
|
+
nativeCurrency: { decimals: 18 },
|
|
468
|
+
}),
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const result = await service._queryGasInfo({
|
|
472
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
473
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
474
|
+
amount: 1,
|
|
475
|
+
chainId: "1",
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
expect(result.success).toBe(true);
|
|
479
|
+
expect(result.fees).toBeDefined();
|
|
480
|
+
expect(result.gasFee).toBeDefined();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("should return default gas fee on error", async () => {
|
|
484
|
+
(service as any).transactions = {
|
|
485
|
+
queryGasInfo: vi.fn().mockResolvedValue({
|
|
486
|
+
success: false,
|
|
487
|
+
message: "Error",
|
|
488
|
+
}),
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const result = await service._queryGasInfo({
|
|
492
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
493
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
494
|
+
amount: 1,
|
|
495
|
+
chainId: "1",
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
expect(result.success).toBe(true);
|
|
499
|
+
expect(result.gasFee).toBeDefined();
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("should use tokenAddress and createErc20TxData when tokenAddress is provided", async () => {
|
|
503
|
+
const mockGasInfo = {
|
|
504
|
+
baseFee: 1000000000,
|
|
505
|
+
gasLimit: 21000,
|
|
506
|
+
priorityFeeLow: 1000000000,
|
|
507
|
+
priorityFeeMedium: 1500000000,
|
|
508
|
+
priorityFeeHigh: 2000000000,
|
|
509
|
+
};
|
|
510
|
+
(service as any).transactions = {
|
|
511
|
+
queryGasInfo: vi.fn().mockResolvedValue({
|
|
512
|
+
success: true,
|
|
513
|
+
data: mockGasInfo,
|
|
514
|
+
}),
|
|
515
|
+
};
|
|
516
|
+
(service as any).networks = {
|
|
517
|
+
getNetworkByChainId: vi.fn().mockResolvedValue({
|
|
518
|
+
nativeCurrency: { decimals: 18 },
|
|
519
|
+
}),
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const result = await service._queryGasInfo({
|
|
523
|
+
from: "0x1234567890123456789012345678901234567890",
|
|
524
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
525
|
+
amount: 100,
|
|
526
|
+
chainId: "1",
|
|
527
|
+
tokenAddress: "0xToken12345678901234567890123456789012",
|
|
528
|
+
decimals: 18,
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
expect(result.success).toBe(true);
|
|
532
|
+
expect(result.fees).toBeDefined();
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
isEvmChain,
|
|
4
|
+
isHexChainId,
|
|
5
|
+
isSameAddress,
|
|
6
|
+
getAllTypeChainIds,
|
|
7
|
+
getAmount,
|
|
8
|
+
decodeErc20Func,
|
|
9
|
+
createErc20TxData,
|
|
10
|
+
buildSubmitTxParams,
|
|
11
|
+
} from "../utils";
|
|
12
|
+
import { encodeFunctionData, erc20Abi } from "viem";
|
|
13
|
+
|
|
14
|
+
describe("evm/utils", () => {
|
|
15
|
+
describe("isEvmChain", () => {
|
|
16
|
+
it("should return true for EVM chain", () => {
|
|
17
|
+
expect(isEvmChain({ platformType: "EVM" })).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return false for non-EVM chain", () => {
|
|
21
|
+
expect(isEvmChain({ platformType: "SOLANA" })).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should return false for null", () => {
|
|
25
|
+
expect(isEvmChain(null)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return false for undefined", () => {
|
|
29
|
+
expect(isEvmChain(undefined)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("isHexChainId", () => {
|
|
34
|
+
it("should return true for hex chain ID", () => {
|
|
35
|
+
expect(isHexChainId("0x1")).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return false for non-hex chain ID", () => {
|
|
39
|
+
expect(isHexChainId("1")).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("isSameAddress", () => {
|
|
44
|
+
it("should return true for same address", () => {
|
|
45
|
+
expect(
|
|
46
|
+
isSameAddress("0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890"),
|
|
47
|
+
).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should return false for different addresses", () => {
|
|
51
|
+
expect(
|
|
52
|
+
isSameAddress("0x1234567890123456789012345678901234567890", "0x9876543210987654321098765432109876543210"),
|
|
53
|
+
).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("getAllTypeChainIds", () => {
|
|
58
|
+
it("should convert hex chain ID", () => {
|
|
59
|
+
const result = getAllTypeChainIds({ chainId: "0x1", chainType: "evm" });
|
|
60
|
+
expect(result.chainId).toBe("1");
|
|
61
|
+
expect(result.chainIdHex).toBe("0x1");
|
|
62
|
+
expect(result.chainUid).toBe("evm:1");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should convert number chain ID", () => {
|
|
66
|
+
const result = getAllTypeChainIds({ chainId: 1, chainType: "evm" });
|
|
67
|
+
expect(result.chainId).toBe("1");
|
|
68
|
+
expect(result.chainIdHex).toBe("0x1");
|
|
69
|
+
expect(result.chainUid).toBe("evm:1");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should convert string number chain ID", () => {
|
|
73
|
+
const result = getAllTypeChainIds({ chainId: "5", chainType: "evm" });
|
|
74
|
+
expect(result.chainId).toBe("5");
|
|
75
|
+
expect(result.chainIdHex).toBe("0x5");
|
|
76
|
+
expect(result.chainUid).toBe("evm:5");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("getAmount", () => {
|
|
81
|
+
it("should convert hex value to amount", () => {
|
|
82
|
+
const amount = getAmount("0xde0b6b3a7640000"); // 1 ETH in hex
|
|
83
|
+
expect(amount).toBeCloseTo(1, 5);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle zero value", () => {
|
|
87
|
+
const amount = getAmount("0x0");
|
|
88
|
+
expect(amount).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("decodeErc20Func", () => {
|
|
93
|
+
it("should decode ERC20 transfer function", () => {
|
|
94
|
+
const transferData = encodeFunctionData({
|
|
95
|
+
abi: erc20Abi,
|
|
96
|
+
functionName: "transfer",
|
|
97
|
+
args: ["0x1234567890123456789012345678901234567890", BigInt(1000)],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const decoded = decodeErc20Func(transferData);
|
|
101
|
+
expect(decoded.functionName).toBe("transfer");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("createErc20TxData", () => {
|
|
106
|
+
it("should create ERC20 transaction data", () => {
|
|
107
|
+
const params = {
|
|
108
|
+
to: "0x9876543210987654321098765432109876543210",
|
|
109
|
+
amount: 1000,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const token = {
|
|
113
|
+
address: "0xTokenAddress1234567890123456789012345678",
|
|
114
|
+
decimals: 18,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const result = createErc20TxData(params, token);
|
|
118
|
+
expect(result.to).toBe(token.address);
|
|
119
|
+
expect(result.amount).toBe(0);
|
|
120
|
+
expect(result.data).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("buildSubmitTxParams", () => {
|
|
125
|
+
it("should use 0 for fromAmount/toAmount when params.amount is missing", async () => {
|
|
126
|
+
const params = {
|
|
127
|
+
txParams: { maxFeePerGas: 1n, gasLimit: 21000n },
|
|
128
|
+
network: { chainIndex: 1 },
|
|
129
|
+
tx: {},
|
|
130
|
+
serializedTransaction: "0x",
|
|
131
|
+
from: "0xFrom",
|
|
132
|
+
to: "0xTo",
|
|
133
|
+
tokenAddress: "0xToken",
|
|
134
|
+
};
|
|
135
|
+
const result = await buildSubmitTxParams(params);
|
|
136
|
+
expect(result.fromAmount).toBe("0");
|
|
137
|
+
expect(result.toAmount).toBe("0");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should use params.amount when provided", async () => {
|
|
141
|
+
const params = {
|
|
142
|
+
amount: 100,
|
|
143
|
+
txParams: { maxFeePerGas: 1n, gasLimit: 21000n },
|
|
144
|
+
network: { chainIndex: 1 },
|
|
145
|
+
tx: {},
|
|
146
|
+
serializedTransaction: "0x",
|
|
147
|
+
from: "0xFrom",
|
|
148
|
+
to: "0xTo",
|
|
149
|
+
tokenAddress: "0xToken",
|
|
150
|
+
};
|
|
151
|
+
const result = await buildSubmitTxParams(params);
|
|
152
|
+
expect(result.fromAmount).toBe("100");
|
|
153
|
+
expect(result.toAmount).toBe("100");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should use gasLimit when maxFeePerGas is absent (covers line 68 branch)", async () => {
|
|
157
|
+
const params = {
|
|
158
|
+
txParams: { gasLimit: 100000n },
|
|
159
|
+
network: { chainIndex: 1 },
|
|
160
|
+
tx: {},
|
|
161
|
+
serializedTransaction: "0x",
|
|
162
|
+
from: "0xFrom",
|
|
163
|
+
to: "0xTo",
|
|
164
|
+
tokenAddress: "0xToken",
|
|
165
|
+
};
|
|
166
|
+
const result = await buildSubmitTxParams(params);
|
|
167
|
+
expect(result.estimatedGas).toBe(100000n);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|