@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,323 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { toSatoshi, toBitcoin, addUsedUtxos, getUsedUtxos, createPsbt, decodePsbt, TransactionParser } from "../utils";
|
|
3
|
+
import * as API from "../rpc";
|
|
4
|
+
import { DogecoinUtils } from "../utils-doge";
|
|
5
|
+
import { Psbt } from "bitcoinjs-lib";
|
|
6
|
+
|
|
7
|
+
vi.mock("../rpc");
|
|
8
|
+
vi.mock("../utils-doge");
|
|
9
|
+
vi.mock("bitcoinjs-lib");
|
|
10
|
+
|
|
11
|
+
// Mock cache
|
|
12
|
+
const mockCache = new Map<string, any>();
|
|
13
|
+
vi.mock("@tomo-inc/wallet-utils", async () => {
|
|
14
|
+
const actual = await vi.importActual("@tomo-inc/wallet-utils");
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
cache: {
|
|
18
|
+
get: vi.fn((key: string) => {
|
|
19
|
+
return mockCache.get(key) || null;
|
|
20
|
+
}),
|
|
21
|
+
set: vi.fn((key: string, value: any, isTemp?: boolean) => {
|
|
22
|
+
mockCache.set(key, value);
|
|
23
|
+
return true;
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("dogecoin/utils Extended Tests", () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
mockCache.clear();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("toSatoshi", () => {
|
|
36
|
+
it("should convert bitcoin to satoshi", () => {
|
|
37
|
+
expect(toSatoshi(1)).toBe(100000000);
|
|
38
|
+
expect(toSatoshi("1.5")).toBe(150000000);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should throw error for invalid amount", () => {
|
|
42
|
+
expect(() => toSatoshi(-1)).toThrow("Invalid amount");
|
|
43
|
+
expect(() => toSatoshi("invalid")).toThrow("Invalid amount");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("toBitcoin", () => {
|
|
48
|
+
it("should convert satoshi to bitcoin", () => {
|
|
49
|
+
expect(toBitcoin(100000000)).toBe(1);
|
|
50
|
+
expect(toBitcoin("150000000")).toBe(1.5);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should throw error for invalid amount", () => {
|
|
54
|
+
expect(() => toBitcoin(-1)).toThrow("Invalid Koinu amount");
|
|
55
|
+
expect(() => toBitcoin("invalid")).toThrow("Invalid Koinu amount");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("addUsedUtxos", () => {
|
|
60
|
+
it("should add used UTXOs to cache", () => {
|
|
61
|
+
const newUsedUtxos = {
|
|
62
|
+
txid1: 1,
|
|
63
|
+
txid2: 1,
|
|
64
|
+
};
|
|
65
|
+
addUsedUtxos(newUsedUtxos);
|
|
66
|
+
const usedUtxos = getUsedUtxos();
|
|
67
|
+
expect(usedUtxos.txid1).toBe(1);
|
|
68
|
+
expect(usedUtxos.txid2).toBe(1);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("getUsedUtxos", () => {
|
|
73
|
+
it("should get used UTXOs from cache", () => {
|
|
74
|
+
const usedUtxos = getUsedUtxos();
|
|
75
|
+
expect(usedUtxos).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("createPsbt", () => {
|
|
80
|
+
it("should create PSBT successfully", async () => {
|
|
81
|
+
const mockUtxos = [
|
|
82
|
+
{
|
|
83
|
+
txid: "txid1",
|
|
84
|
+
vout: 0,
|
|
85
|
+
outputValue: 101000000, // 1.01 DOGE (enough for 1.0 + 0.001 fee)
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({
|
|
90
|
+
hex: "hex123",
|
|
91
|
+
} as any);
|
|
92
|
+
|
|
93
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
94
|
+
|
|
95
|
+
const result = await createPsbt({
|
|
96
|
+
from: "DAddress123",
|
|
97
|
+
to: "DAddress456",
|
|
98
|
+
amount: 1.0,
|
|
99
|
+
fee: 0.001,
|
|
100
|
+
spendableUtxos: mockUtxos,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(result.psbtBase64).toBe("psbtBase64");
|
|
104
|
+
expect(result.usingUtxos).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should throw error when no spendable UTXOs", async () => {
|
|
108
|
+
await expect(
|
|
109
|
+
createPsbt({
|
|
110
|
+
from: "DAddress123",
|
|
111
|
+
to: "DAddress456",
|
|
112
|
+
amount: 1.0,
|
|
113
|
+
fee: 0.001,
|
|
114
|
+
spendableUtxos: [],
|
|
115
|
+
}),
|
|
116
|
+
).rejects.toThrow("no spendable utxos");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should throw error when not enough funds", async () => {
|
|
120
|
+
const mockUtxos = [
|
|
121
|
+
{
|
|
122
|
+
txid: "txid1",
|
|
123
|
+
vout: 0,
|
|
124
|
+
outputValue: 1000, // Not enough
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
await expect(
|
|
129
|
+
createPsbt({
|
|
130
|
+
from: "DAddress123",
|
|
131
|
+
to: "DAddress456",
|
|
132
|
+
amount: 1.0,
|
|
133
|
+
fee: 0.001,
|
|
134
|
+
spendableUtxos: mockUtxos,
|
|
135
|
+
}),
|
|
136
|
+
).rejects.toThrow("not enough funds");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should return utxoDetail null when getTxDetail fails for one utxo (covers line 97)", async () => {
|
|
140
|
+
const mockUtxos = [
|
|
141
|
+
{ txid: "txid1", vout: 0, outputValue: 60000000 },
|
|
142
|
+
{ txid: "txid2", vout: 0, outputValue: 50000000 },
|
|
143
|
+
];
|
|
144
|
+
vi.mocked(API.getTxDetail)
|
|
145
|
+
.mockResolvedValueOnce({ hex: "hex1" } as any)
|
|
146
|
+
.mockRejectedValueOnce(new Error("fetch failed"));
|
|
147
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
148
|
+
|
|
149
|
+
const result = await createPsbt({
|
|
150
|
+
from: "DAddress123",
|
|
151
|
+
to: "DAddress456",
|
|
152
|
+
amount: 0.5,
|
|
153
|
+
fee: 0.001,
|
|
154
|
+
spendableUtxos: mockUtxos,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result.psbtBase64).toBe("psbtBase64");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should throw when fetched details leave insufficient inputs (covers line 127)", async () => {
|
|
161
|
+
const mockUtxos = [
|
|
162
|
+
{ txid: "txid1", vout: 0, outputValue: 50000000 },
|
|
163
|
+
{ txid: "txid2", vout: 0, outputValue: 50000000 },
|
|
164
|
+
];
|
|
165
|
+
vi.mocked(API.getTxDetail)
|
|
166
|
+
.mockRejectedValueOnce(new Error("fetch failed"))
|
|
167
|
+
.mockResolvedValueOnce({ hex: "hex2" } as any);
|
|
168
|
+
|
|
169
|
+
await expect(
|
|
170
|
+
createPsbt({
|
|
171
|
+
from: "DAddress123",
|
|
172
|
+
to: "DAddress456",
|
|
173
|
+
amount: 1.0,
|
|
174
|
+
fee: 0.001,
|
|
175
|
+
spendableUtxos: mockUtxos,
|
|
176
|
+
}),
|
|
177
|
+
).rejects.toThrow("not enough funds to cover amount and fee");
|
|
178
|
+
|
|
179
|
+
vi.mocked(API.getTxDetail).mockReset();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should throw when all getTxDetail fail so no inputs added (covers line 97 and 127)", async () => {
|
|
183
|
+
const mockUtxos = [
|
|
184
|
+
{ txid: "txid1", vout: 0, outputValue: 60000000 },
|
|
185
|
+
{ txid: "txid2", vout: 0, outputValue: 50000000 },
|
|
186
|
+
];
|
|
187
|
+
vi.mocked(API.getTxDetail).mockRejectedValue(new Error("network error"));
|
|
188
|
+
|
|
189
|
+
await expect(
|
|
190
|
+
createPsbt({
|
|
191
|
+
from: "DAddress123",
|
|
192
|
+
to: "DAddress456",
|
|
193
|
+
amount: 0.5,
|
|
194
|
+
fee: 0.001,
|
|
195
|
+
spendableUtxos: mockUtxos,
|
|
196
|
+
}),
|
|
197
|
+
).rejects.toThrow("not enough funds to cover amount and fee");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should add change output when needed", async () => {
|
|
201
|
+
const mockUtxos = [
|
|
202
|
+
{
|
|
203
|
+
txid: "txid1",
|
|
204
|
+
vout: 0,
|
|
205
|
+
outputValue: 200000000, // More than needed
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({
|
|
210
|
+
hex: "hex123",
|
|
211
|
+
} as any);
|
|
212
|
+
|
|
213
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
214
|
+
|
|
215
|
+
const result = await createPsbt({
|
|
216
|
+
from: "DAddress123",
|
|
217
|
+
to: "DAddress456",
|
|
218
|
+
amount: 1.0,
|
|
219
|
+
fee: 0.001,
|
|
220
|
+
spendableUtxos: mockUtxos,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(result).toBeDefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should break when addedAmount >= totalNeeded (covers line 109)", async () => {
|
|
227
|
+
const mockUtxos = [
|
|
228
|
+
{ txid: "txid1", vout: 0, outputValue: 60000000 },
|
|
229
|
+
{ txid: "txid2", vout: 0, outputValue: 50000000 },
|
|
230
|
+
];
|
|
231
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex1" } as any);
|
|
232
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
233
|
+
|
|
234
|
+
const result = await createPsbt({
|
|
235
|
+
from: "DAddress123",
|
|
236
|
+
to: "DAddress456",
|
|
237
|
+
amount: 0.5,
|
|
238
|
+
fee: 0.001,
|
|
239
|
+
spendableUtxos: mockUtxos,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(result.psbtBase64).toBe("psbtBase64");
|
|
243
|
+
expect(API.getTxDetail).toHaveBeenCalledTimes(1);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("decodePsbt", () => {
|
|
248
|
+
it("should decode PSBT from base64", () => {
|
|
249
|
+
vi.mocked(Psbt.fromHex).mockReturnValue({} as any);
|
|
250
|
+
|
|
251
|
+
const psbt = decodePsbt("base64psbt", "base64");
|
|
252
|
+
expect(psbt).toBeDefined();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should decode PSBT from hex", () => {
|
|
256
|
+
vi.mocked(Psbt.fromHex).mockReturnValue({} as any);
|
|
257
|
+
|
|
258
|
+
const psbt = decodePsbt("hexpsbt", "hex");
|
|
259
|
+
expect(psbt).toBeDefined();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should throw error on decode failure", () => {
|
|
263
|
+
vi.mocked(Psbt.fromHex).mockImplementation(() => {
|
|
264
|
+
throw new Error("Invalid PSBT");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(() => decodePsbt("invalid", "hex")).toThrow("Invalid PSBT");
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("TransactionParser", () => {
|
|
272
|
+
it("should create parser instance", () => {
|
|
273
|
+
const parser = new TransactionParser("rawTx123");
|
|
274
|
+
expect(parser.rawTx).toBe("rawTx123");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe("hexToText", () => {
|
|
278
|
+
it("should convert hex to text", () => {
|
|
279
|
+
const parser = new TransactionParser("");
|
|
280
|
+
const text = parser.hexToText("48656c6c6f"); // "Hello" in hex
|
|
281
|
+
expect(text).toBe("Hello");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("extractOPReturnData", () => {
|
|
286
|
+
it("should extract OP_RETURN data", () => {
|
|
287
|
+
const rawTx = "6f72647b2274797065223a2274657374227d"; // 'ord{"type":"test"}'
|
|
288
|
+
const parser = new TransactionParser(rawTx);
|
|
289
|
+
const data = parser.extractOPReturnData();
|
|
290
|
+
expect(data).toBeDefined();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should return null when no OP_RETURN data", () => {
|
|
294
|
+
const parser = new TransactionParser("rawTx123");
|
|
295
|
+
const data = parser.extractOPReturnData();
|
|
296
|
+
expect(data).toBeNull();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("parseInputs", () => {
|
|
301
|
+
it("should parse inputs", () => {
|
|
302
|
+
const rawTx =
|
|
303
|
+
"01000000" + "01" + "txid1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + "00000000";
|
|
304
|
+
const parser = new TransactionParser(rawTx);
|
|
305
|
+
const inputs = parser.parseInputs();
|
|
306
|
+
expect(inputs).toBeDefined();
|
|
307
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe("parseScript", () => {
|
|
312
|
+
it("should parse script", () => {
|
|
313
|
+
const rawTx =
|
|
314
|
+
"01000000" + "01" + "txid1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + "00000000";
|
|
315
|
+
const parser = new TransactionParser(rawTx);
|
|
316
|
+
const script = parser.parseScript();
|
|
317
|
+
expect(script.version).toBeDefined();
|
|
318
|
+
expect(script.inputCount).toBeDefined();
|
|
319
|
+
expect(script.inputs).toBeDefined();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
});
|
package/src/dogecoin/base.ts
CHANGED
package/src/dogecoin/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChainTypeEnum, SupportedChainTypes } from "@tomo-inc/wallet-utils";
|
|
2
2
|
|
|
3
|
-
export const BaseConfig = SupportedChainTypes[ChainTypeEnum.
|
|
3
|
+
export const BaseConfig = SupportedChainTypes[ChainTypeEnum.DOGECOIN] as any;
|
|
4
4
|
|
|
5
5
|
export const BLOCK_CONFIRMATIONS = 1;
|
|
6
6
|
export const FEE_RATE_KB = 0.5;
|
|
@@ -18,7 +18,7 @@ export const NETWORK = "livenet";
|
|
|
18
18
|
export const MYDOGE_BASE_URL = "https://api.mydoge.com";
|
|
19
19
|
export const TX_LINK = "https://chain.so/tx/DOGE/";
|
|
20
20
|
|
|
21
|
-
export const RPC_URL_TOMO = "https://wallet-
|
|
21
|
+
export const RPC_URL_TOMO = "https://wallet-test.tomo.inc/rpc/v1/doge_coin";
|
|
22
22
|
export const RPC_URL = "https://api.bitcore.io/api/DOGE/mainnet";
|
|
23
23
|
export const RPC_TIMEOUT = 20 * 1000;
|
|
24
24
|
|
package/src/dogecoin/rpc.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
BLOCK_CONFIRMATIONS,
|
|
5
|
+
FEE_RATE_KB,
|
|
6
|
+
MYDOGE_BASE_URL,
|
|
7
|
+
RPC_TIMEOUT,
|
|
8
|
+
RPC_URL,
|
|
9
|
+
RPC_URL_TOMO,
|
|
10
|
+
TRANSACTION_PAGE_SIZE,
|
|
11
|
+
TX_SIZE,
|
|
12
|
+
} from "./config";
|
|
4
13
|
import { DogeSpendableUtxos } from "./type";
|
|
5
14
|
import { TransactionParser, toBitcoin, toSatoshi } from "./utils";
|
|
6
15
|
|
package/src/dogecoin/service.ts
CHANGED
|
@@ -114,11 +114,7 @@ export class DogecoinService extends BaseService {
|
|
|
114
114
|
addressList: [txData.from],
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
-
const {
|
|
118
|
-
data: gasInfo,
|
|
119
|
-
success,
|
|
120
|
-
message,
|
|
121
|
-
}: any = await this.transactions.queryGasInfo({
|
|
117
|
+
const { data: gasInfo, success }: any = await this.transactions.queryGasInfo({
|
|
122
118
|
chainType: this.chainType,
|
|
123
119
|
params: queryGasParams,
|
|
124
120
|
});
|
|
@@ -183,6 +179,10 @@ export class DogecoinService extends BaseService {
|
|
|
183
179
|
const psbtBase64 = base.toBase64(base.fromHex(psbtHex));
|
|
184
180
|
const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify({ psbtBase64 }));
|
|
185
181
|
|
|
182
|
+
if (!options) {
|
|
183
|
+
console.warn("not support options.");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
186
|
if (!signedPsbt) {
|
|
187
187
|
throw new Error("error psbtHex:" + psbtHex);
|
|
188
188
|
}
|
|
@@ -192,6 +192,7 @@ export class DogecoinService extends BaseService {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
public async signPsbts({ psbtHexs, options }: { psbtHexs: string[]; options?: any }): Promise<string[]> {
|
|
195
|
+
console.warn("signPsbsts:", psbtHexs, options);
|
|
195
196
|
throw new Error("not implemented");
|
|
196
197
|
}
|
|
197
198
|
|
|
@@ -233,6 +234,9 @@ export class DogecoinService extends BaseService {
|
|
|
233
234
|
fee: number; //satoshis
|
|
234
235
|
options?: { feeRate: number; memo?: string };
|
|
235
236
|
}): Promise<string> {
|
|
237
|
+
if (options) {
|
|
238
|
+
console.warn("not support options");
|
|
239
|
+
}
|
|
236
240
|
if (fee <= 0) {
|
|
237
241
|
throw new Error("fee is required");
|
|
238
242
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { getRPCClient, getERC20Contract, decodeErc20Func, createErc20TxData } from "../rpc";
|
|
3
|
+
|
|
4
|
+
// Mock viem
|
|
5
|
+
vi.mock("viem", async () => {
|
|
6
|
+
const actual = await vi.importActual("viem");
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
createPublicClient: vi.fn(() => ({
|
|
10
|
+
getBalance: vi.fn(),
|
|
11
|
+
estimateGas: vi.fn(),
|
|
12
|
+
getTransactionCount: vi.fn(),
|
|
13
|
+
sendRawTransaction: vi.fn(),
|
|
14
|
+
getTransaction: vi.fn(),
|
|
15
|
+
})),
|
|
16
|
+
http: vi.fn(),
|
|
17
|
+
getContract: vi.fn(() => ({
|
|
18
|
+
read: {
|
|
19
|
+
balanceOf: vi.fn(),
|
|
20
|
+
decimals: vi.fn(),
|
|
21
|
+
symbol: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
write: {
|
|
24
|
+
transfer: vi.fn(),
|
|
25
|
+
},
|
|
26
|
+
})),
|
|
27
|
+
encodeFunctionData: vi.fn(() => "0x1234"),
|
|
28
|
+
decodeFunctionData: vi.fn(() => ({
|
|
29
|
+
functionName: "transfer",
|
|
30
|
+
args: ["0xRecipient", 100n],
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("evm rpc", () => {
|
|
36
|
+
describe("getRPCClient", () => {
|
|
37
|
+
it("should create RPC client with network config", () => {
|
|
38
|
+
const network = {
|
|
39
|
+
chainId: 1,
|
|
40
|
+
name: "Ethereum",
|
|
41
|
+
rpcUrls: ["https://rpc.ethereum.org"],
|
|
42
|
+
nativeCurrencyDecimals: 18,
|
|
43
|
+
nativeCurrencyName: "Ether",
|
|
44
|
+
nativeCurrencySymbol: "ETH",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const result = getRPCClient(network);
|
|
48
|
+
expect(result).toHaveProperty("rpcClient");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should handle network with number chainId", () => {
|
|
52
|
+
const network = {
|
|
53
|
+
chainId: 56,
|
|
54
|
+
name: "BSC",
|
|
55
|
+
rpcUrls: ["https://rpc.bsc.com"],
|
|
56
|
+
nativeCurrencyDecimals: 18,
|
|
57
|
+
nativeCurrencyName: "BNB",
|
|
58
|
+
nativeCurrencySymbol: "BNB",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const result = getRPCClient(network);
|
|
62
|
+
expect(result).toHaveProperty("rpcClient");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should use chainId as-is when Number(chainId) is 0", () => {
|
|
66
|
+
const network = {
|
|
67
|
+
chainId: 0,
|
|
68
|
+
name: "Local",
|
|
69
|
+
rpcUrls: ["http://127.0.0.1:8545"],
|
|
70
|
+
nativeCurrencyDecimals: 18,
|
|
71
|
+
nativeCurrencyName: "ETH",
|
|
72
|
+
nativeCurrencySymbol: "ETH",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const result = getRPCClient(network);
|
|
76
|
+
expect(result).toHaveProperty("rpcClient");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("getERC20Contract", () => {
|
|
81
|
+
it("should get ERC20 contract", () => {
|
|
82
|
+
const mockClient = {} as any;
|
|
83
|
+
const address = "0x1234567890123456789012345678901234567890" as `0x${string}`;
|
|
84
|
+
|
|
85
|
+
const result = getERC20Contract(mockClient, address);
|
|
86
|
+
expect(result).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("decodeErc20Func", () => {
|
|
91
|
+
it("should decode ERC20 function data", () => {
|
|
92
|
+
const data =
|
|
93
|
+
"0xa9059cbb00000000000000000000000012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000064" as `0x${string}`;
|
|
94
|
+
|
|
95
|
+
const result = decodeErc20Func(data);
|
|
96
|
+
expect(result).toBeDefined();
|
|
97
|
+
expect(result.functionName).toBe("transfer");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("createErc20TxData", () => {
|
|
102
|
+
it("should create ERC20 transaction data", () => {
|
|
103
|
+
const params = {
|
|
104
|
+
to: "0xRecipient1234567890123456789012345678901234" as `0x${string}`,
|
|
105
|
+
amount: "100",
|
|
106
|
+
};
|
|
107
|
+
const token = {
|
|
108
|
+
address: "0xToken1234567890123456789012345678901234" as `0x${string}`,
|
|
109
|
+
decimals: 18,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const result = createErc20TxData(params, token);
|
|
113
|
+
expect(result).toHaveProperty("data");
|
|
114
|
+
expect(result.amount).toBe(0);
|
|
115
|
+
expect(result.to).toBe(token.address);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should handle different decimal values", () => {
|
|
119
|
+
const params = {
|
|
120
|
+
to: "0xRecipient1234567890123456789012345678901234" as `0x${string}`,
|
|
121
|
+
amount: "100",
|
|
122
|
+
};
|
|
123
|
+
const token = {
|
|
124
|
+
address: "0xToken1234567890123456789012345678901234" as `0x${string}`,
|
|
125
|
+
decimals: 6,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const result = createErc20TxData(params, token);
|
|
129
|
+
expect(result).toHaveProperty("data");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|