@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,420 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { DogecoinService } from "../service";
|
|
3
|
+
import { ChainTypeEnum } from "@tomo-inc/wallet-utils";
|
|
4
|
+
import { IAccountInfo, TomoAppInfo } from "../../types";
|
|
5
|
+
import * as API from "../rpc";
|
|
6
|
+
import { DogecoinUtils } from "../utils-doge";
|
|
7
|
+
|
|
8
|
+
vi.mock("../rpc");
|
|
9
|
+
vi.mock("../utils-doge");
|
|
10
|
+
|
|
11
|
+
describe("DogecoinService Extended Tests", () => {
|
|
12
|
+
let mockAccountInfo: IAccountInfo;
|
|
13
|
+
let mockTomoAppInfo: TomoAppInfo;
|
|
14
|
+
let service: DogecoinService;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
(DogecoinService as any).instance = undefined;
|
|
19
|
+
|
|
20
|
+
mockAccountInfo = {
|
|
21
|
+
getCurrent: vi.fn().mockResolvedValue([
|
|
22
|
+
{
|
|
23
|
+
address: "DAddress123",
|
|
24
|
+
publicKey: "test-public-key",
|
|
25
|
+
},
|
|
26
|
+
]),
|
|
27
|
+
signMessage: vi.fn().mockResolvedValue("signature123"),
|
|
28
|
+
signTransaction: vi.fn().mockResolvedValue("signedPsbt123"),
|
|
29
|
+
} as any;
|
|
30
|
+
|
|
31
|
+
mockTomoAppInfo = {
|
|
32
|
+
tomoStage: "dev",
|
|
33
|
+
appId: "test-app",
|
|
34
|
+
appName: "Test App",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("getBalance", () => {
|
|
41
|
+
it("should get balance successfully", async () => {
|
|
42
|
+
vi.mocked(API.getBalance).mockResolvedValue({ balance: 1000000 });
|
|
43
|
+
|
|
44
|
+
const result = await service.getBalance();
|
|
45
|
+
expect(result.confirmed).toBe(1000000);
|
|
46
|
+
expect(result.unconfirmed).toBe(0);
|
|
47
|
+
expect(result.total).toBe(1000000);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should throw error when address is not set", async () => {
|
|
51
|
+
mockAccountInfo.getCurrent = vi.fn().mockResolvedValue([]);
|
|
52
|
+
await expect(service.getBalance()).rejects.toThrow("address is not set");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("requestPsbt", () => {
|
|
57
|
+
it("should request PSBT successfully", async () => {
|
|
58
|
+
vi.mocked(DogecoinUtils.rawTxToPsbtBase64).mockResolvedValue("psbtBase64");
|
|
59
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedRawTx");
|
|
60
|
+
vi.mocked(API.sendDogeTx).mockResolvedValue({ txid: "txid123" });
|
|
61
|
+
|
|
62
|
+
const result = await service.requestPsbt({
|
|
63
|
+
rawTx: "rawTx123",
|
|
64
|
+
signOnly: false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(result).toBeDefined();
|
|
68
|
+
expect(result?.signedRawTx).toBe("signedRawTx");
|
|
69
|
+
expect(result?.txId).toBe("txid123");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should return only signedRawTx when signOnly is true", async () => {
|
|
73
|
+
vi.mocked(DogecoinUtils.rawTxToPsbtBase64).mockResolvedValue("psbtBase64");
|
|
74
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedRawTx");
|
|
75
|
+
|
|
76
|
+
const result = await service.requestPsbt({
|
|
77
|
+
rawTx: "rawTx123",
|
|
78
|
+
signOnly: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(result?.signedRawTx).toBe("signedRawTx");
|
|
82
|
+
expect(result?.txId).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should return null when signedPsbt is empty", async () => {
|
|
86
|
+
mockAccountInfo.signTransaction = vi.fn().mockResolvedValue("");
|
|
87
|
+
vi.mocked(DogecoinUtils.rawTxToPsbtBase64).mockResolvedValue("psbtBase64");
|
|
88
|
+
|
|
89
|
+
const result = await service.requestPsbt({
|
|
90
|
+
rawTx: "rawTx123",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(result).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should send transaction when signOnly is false", async () => {
|
|
97
|
+
vi.mocked(DogecoinUtils.rawTxToPsbtBase64).mockResolvedValue("psbtBase64");
|
|
98
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedRawTx");
|
|
99
|
+
vi.mocked(API.sendDogeTx).mockResolvedValue({ txid: "txid123" });
|
|
100
|
+
|
|
101
|
+
const result = await service.requestPsbt({
|
|
102
|
+
rawTx: "rawTx123",
|
|
103
|
+
signOnly: false,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(result?.txId).toBe("txid123");
|
|
107
|
+
expect(API.sendDogeTx).toHaveBeenCalledWith("signedRawTx");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("signPsbt", () => {
|
|
112
|
+
it("should sign PSBT successfully", async () => {
|
|
113
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedRawTx");
|
|
114
|
+
|
|
115
|
+
const result = await service.signPsbt({ psbtHex: "psbtHex123" });
|
|
116
|
+
expect(result).toBe("signedRawTx");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should throw error when signedPsbt is empty", async () => {
|
|
120
|
+
mockAccountInfo.signTransaction = vi.fn().mockResolvedValue("");
|
|
121
|
+
|
|
122
|
+
await expect(service.signPsbt({ psbtHex: "psbtHex123" })).rejects.toThrow("error psbtHex:psbtHex123");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("signPsbts", () => {
|
|
127
|
+
it("should throw error", async () => {
|
|
128
|
+
await expect(service.signPsbts({ psbtHexs: [] })).rejects.toThrow("not implemented");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("requestTransaction", () => {
|
|
133
|
+
it("should request transaction successfully", async () => {
|
|
134
|
+
const mockTxData = {
|
|
135
|
+
from: "DAddress123",
|
|
136
|
+
to: "DAddress456",
|
|
137
|
+
amount: 1.0,
|
|
138
|
+
fee: 0.001,
|
|
139
|
+
spendableUtxos: [
|
|
140
|
+
{
|
|
141
|
+
txid: "txid123",
|
|
142
|
+
vout: 0,
|
|
143
|
+
outputValue: 101000000, // 1.01 DOGE (enough for 1.0 + 0.001 fee)
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
vi.mocked(API.getSpendableUtxos).mockResolvedValue([]);
|
|
149
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex123" } as any);
|
|
150
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
151
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedTx");
|
|
152
|
+
vi.mocked(API.sendDogeTx).mockResolvedValue({ txid: "txid123" });
|
|
153
|
+
|
|
154
|
+
const result = await service.requestTransaction(mockTxData as any);
|
|
155
|
+
expect(result?.txId).toBe("txid123");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should throw error when amountMismatch", async () => {
|
|
159
|
+
const mockTxData = {
|
|
160
|
+
from: "DAddress123",
|
|
161
|
+
to: "DAddress456",
|
|
162
|
+
amount: 1.0,
|
|
163
|
+
fee: 0.001,
|
|
164
|
+
amountMismatch: true,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
await expect(service.requestTransaction(mockTxData as any)).rejects.toThrow("balance_insufficient");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should fetch spendableUtxos when not provided", async () => {
|
|
171
|
+
const mockTxData = {
|
|
172
|
+
from: "DAddress123",
|
|
173
|
+
to: "DAddress456",
|
|
174
|
+
amount: 1.0,
|
|
175
|
+
fee: 0.001,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
vi.mocked(API.getSpendableUtxos).mockResolvedValue([
|
|
179
|
+
{
|
|
180
|
+
txid: "txid123",
|
|
181
|
+
vout: 0,
|
|
182
|
+
outputValue: 101000000, // 1.01 DOGE (enough for 1.0 + 0.001 fee)
|
|
183
|
+
},
|
|
184
|
+
]);
|
|
185
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex123" } as any);
|
|
186
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
187
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedTx");
|
|
188
|
+
vi.mocked(API.sendDogeTx).mockResolvedValue({ txid: "txid123" });
|
|
189
|
+
|
|
190
|
+
await service.requestTransaction(mockTxData as any);
|
|
191
|
+
expect(API.getSpendableUtxos).toHaveBeenCalledWith("DAddress123");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should throw error when signedTx is empty", async () => {
|
|
195
|
+
const mockTxData = {
|
|
196
|
+
from: "DAddress123",
|
|
197
|
+
to: "DAddress456",
|
|
198
|
+
amount: 1.0,
|
|
199
|
+
fee: 0.001,
|
|
200
|
+
spendableUtxos: [
|
|
201
|
+
{
|
|
202
|
+
txid: "txid123",
|
|
203
|
+
vout: 0,
|
|
204
|
+
outputValue: 101000000, // 1.01 DOGE (enough for 1.0 + 0.001 fee)
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex123" } as any);
|
|
210
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
211
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("");
|
|
212
|
+
|
|
213
|
+
await expect(service.requestTransaction(mockTxData as any)).rejects.toThrow("Error: sign transaction err.");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should throw error when send transaction fails", async () => {
|
|
217
|
+
const mockTxData = {
|
|
218
|
+
from: "DAddress123",
|
|
219
|
+
to: "DAddress456",
|
|
220
|
+
amount: 1.0,
|
|
221
|
+
fee: 0.001,
|
|
222
|
+
spendableUtxos: [
|
|
223
|
+
{
|
|
224
|
+
txid: "txid123",
|
|
225
|
+
vout: 0,
|
|
226
|
+
outputValue: 101000000, // 1.01 DOGE (enough for 1.0 + 0.001 fee)
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex123" } as any);
|
|
232
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
233
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedTx");
|
|
234
|
+
vi.mocked(API.sendDogeTx).mockRejectedValue(new Error("Network error"));
|
|
235
|
+
|
|
236
|
+
await expect(service.requestTransaction(mockTxData as any)).rejects.toThrow("Error: send transaction err.");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("send", () => {
|
|
241
|
+
it("should send successfully", async () => {
|
|
242
|
+
vi.mocked(API.getSpendableUtxos).mockResolvedValue([
|
|
243
|
+
{
|
|
244
|
+
txid: "txid123",
|
|
245
|
+
vout: 0,
|
|
246
|
+
outputValue: 101000000, // 1.01 DOGE (enough for 1.0 + 0.001 fee)
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex123" } as any);
|
|
250
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
251
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedTx");
|
|
252
|
+
vi.mocked(API.sendDogeTx).mockResolvedValue({ txid: "txid123" });
|
|
253
|
+
|
|
254
|
+
const result = await service.send({
|
|
255
|
+
toAddress: "DAddress456",
|
|
256
|
+
amount: 100000000, // 1.0 DOGE
|
|
257
|
+
fee: 1000000, // 0.001 DOGE
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(result).toBe("txid123");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should throw error when fee is not provided", async () => {
|
|
264
|
+
await expect(
|
|
265
|
+
service.send({
|
|
266
|
+
toAddress: "DAddress456",
|
|
267
|
+
amount: 100000000,
|
|
268
|
+
fee: 0,
|
|
269
|
+
}),
|
|
270
|
+
).rejects.toThrow("fee is required");
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("sendDogecoin", () => {
|
|
275
|
+
it("should call send method", async () => {
|
|
276
|
+
vi.mocked(API.getSpendableUtxos).mockResolvedValue([
|
|
277
|
+
{
|
|
278
|
+
txid: "txid123",
|
|
279
|
+
vout: 0,
|
|
280
|
+
outputValue: 101000000, // 1.01 DOGE (enough for 1.0 + 0.001 fee)
|
|
281
|
+
},
|
|
282
|
+
]);
|
|
283
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex123" } as any);
|
|
284
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
285
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedTx");
|
|
286
|
+
vi.mocked(API.sendDogeTx).mockResolvedValue({ txid: "txid123" });
|
|
287
|
+
|
|
288
|
+
const result = await service.sendDogecoin({
|
|
289
|
+
toAddress: "DAddress456",
|
|
290
|
+
amount: 100000000, // 1.0 DOGE
|
|
291
|
+
fee: 1000000, // 0.001 DOGE
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
expect(result).toBe("txid123");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("should warn when options are passed (not supported)", async () => {
|
|
298
|
+
vi.mocked(API.getSpendableUtxos).mockResolvedValue([
|
|
299
|
+
{
|
|
300
|
+
txid: "txid123",
|
|
301
|
+
vout: 0,
|
|
302
|
+
outputValue: 101000000,
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
vi.mocked(API.getTxDetail).mockResolvedValue({ hex: "hex123" } as any);
|
|
306
|
+
vi.mocked(DogecoinUtils.buildPsbtToBase64).mockReturnValue("psbtBase64");
|
|
307
|
+
vi.mocked(DogecoinUtils.extractPsbtTransaction).mockReturnValue("signedTx");
|
|
308
|
+
vi.mocked(API.sendDogeTx).mockResolvedValue({ txid: "txid123" });
|
|
309
|
+
|
|
310
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
311
|
+
const result = await service.sendDogecoin({
|
|
312
|
+
toAddress: "DAddress456",
|
|
313
|
+
amount: 100000000,
|
|
314
|
+
fee: 1000000,
|
|
315
|
+
options: { feeRate: 1, memo: "test" },
|
|
316
|
+
});
|
|
317
|
+
expect(warnSpy).toHaveBeenCalledWith("not support options");
|
|
318
|
+
warnSpy.mockRestore();
|
|
319
|
+
expect(result).toBe("txid123");
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe("getTransactionStatus", () => {
|
|
324
|
+
it("should get transaction status", async () => {
|
|
325
|
+
const mockTx = {
|
|
326
|
+
txid: "txid123",
|
|
327
|
+
confirmations: 5,
|
|
328
|
+
vout: [
|
|
329
|
+
{
|
|
330
|
+
value: 1.0,
|
|
331
|
+
addresses: ["DAddress456"],
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
blockTime: 1234567890,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
vi.mocked(API.getTxDetail).mockResolvedValue(mockTx as any);
|
|
338
|
+
|
|
339
|
+
const result = await service.getTransactionStatus({ txId: "txid123" });
|
|
340
|
+
expect(result.txId).toBe("txid123");
|
|
341
|
+
expect(result.status).toBe("confirmed");
|
|
342
|
+
expect(result.confirmations).toBe(5);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("should return null when transaction not found", async () => {
|
|
346
|
+
vi.mocked(API.getTxDetail).mockResolvedValue(null as any);
|
|
347
|
+
|
|
348
|
+
const result = await service.getTransactionStatus({ txId: "txid123" });
|
|
349
|
+
expect(result).toBeNull();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should return pending status when confirmations is 0", async () => {
|
|
353
|
+
const mockTx = {
|
|
354
|
+
txid: "txid123",
|
|
355
|
+
confirmations: 0,
|
|
356
|
+
vout: [
|
|
357
|
+
{
|
|
358
|
+
value: 1.0,
|
|
359
|
+
addresses: ["DAddress456"],
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
blockTime: 1234567890,
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
vi.mocked(API.getTxDetail).mockResolvedValue(mockTx as any);
|
|
366
|
+
|
|
367
|
+
const result = await service.getTransactionStatus({ txId: "txid123" });
|
|
368
|
+
expect(result.status).toBe("pending");
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe("_queryGasInfo", () => {
|
|
373
|
+
it("should query gas info successfully", async () => {
|
|
374
|
+
const mockGasInfo = {
|
|
375
|
+
baseFee: 1,
|
|
376
|
+
gasLimit: 100000,
|
|
377
|
+
priorityFeeLow: 1000,
|
|
378
|
+
priorityFeeMedium: 2000,
|
|
379
|
+
priorityFeeHigh: 3000,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
(service as any).transactions = {
|
|
383
|
+
queryGasInfo: vi.fn().mockResolvedValue({
|
|
384
|
+
success: true,
|
|
385
|
+
data: mockGasInfo,
|
|
386
|
+
}),
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const result = await service._queryGasInfo({
|
|
390
|
+
from: "DAddress123",
|
|
391
|
+
to: "DAddress456",
|
|
392
|
+
amount: 1.0,
|
|
393
|
+
chainId: "1",
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
expect(result.success).toBe(true);
|
|
397
|
+
expect(result.fees).toBeDefined();
|
|
398
|
+
expect(result.gasFee).toBeDefined();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("should return default gas fee on error", async () => {
|
|
402
|
+
(service as any).transactions = {
|
|
403
|
+
queryGasInfo: vi.fn().mockResolvedValue({
|
|
404
|
+
success: false,
|
|
405
|
+
message: "Error",
|
|
406
|
+
}),
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const result = await service._queryGasInfo({
|
|
410
|
+
from: "DAddress123",
|
|
411
|
+
to: "DAddress456",
|
|
412
|
+
amount: 1.0,
|
|
413
|
+
chainId: "1",
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(result.success).toBe(true);
|
|
417
|
+
expect(result.gasFee).toBeDefined();
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
});
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { DogecoinUtils, DogecoinAddress } from "../utils-doge";
|
|
3
|
+
import * as rpc from "../rpc";
|
|
4
|
+
import { Psbt, Transaction } from "bitcoinjs-lib";
|
|
5
|
+
|
|
6
|
+
// Mock rpc
|
|
7
|
+
vi.mock("../rpc", () => ({
|
|
8
|
+
getTxDetail: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe("DogecoinUtils", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("DogecoinAddress", () => {
|
|
17
|
+
describe("generatePath", () => {
|
|
18
|
+
it("should generate path for address index", () => {
|
|
19
|
+
const path = DogecoinAddress.generatePath(0);
|
|
20
|
+
expect(path).toBe("m/44'/3'/0'/0/0");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should generate path for non-zero address index", () => {
|
|
24
|
+
const path = DogecoinAddress.generatePath(5);
|
|
25
|
+
expect(path).toBe("m/44'/3'/0'/0/5");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("verifyAddress", () => {
|
|
30
|
+
it("should verify valid dogecoin address", () => {
|
|
31
|
+
// This is a valid dogecoin address format
|
|
32
|
+
const validAddress = "DTest123456789012345678901234567890";
|
|
33
|
+
// Note: This will fail in actual implementation if address is not valid
|
|
34
|
+
// We're just testing the function exists and can be called
|
|
35
|
+
const result = DogecoinAddress.verifyAddress(validAddress);
|
|
36
|
+
expect(typeof result).toBe("boolean");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return false for invalid address", () => {
|
|
40
|
+
const invalidAddress = "invalid";
|
|
41
|
+
const result = DogecoinAddress.verifyAddress(invalidAddress);
|
|
42
|
+
expect(result).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("buildPsbtToBase64", () => {
|
|
48
|
+
it("should throw error for invalid transaction data", () => {
|
|
49
|
+
// Test with invalid txId (too short)
|
|
50
|
+
const tx = {
|
|
51
|
+
address: "DTest123",
|
|
52
|
+
inputs: [
|
|
53
|
+
{
|
|
54
|
+
txId: "invalid", // Invalid txId
|
|
55
|
+
vOut: 0,
|
|
56
|
+
amount: 100000000,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
outputs: [
|
|
60
|
+
{
|
|
61
|
+
address: "DRecipient123",
|
|
62
|
+
amount: 50000000,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
expect(() => {
|
|
68
|
+
DogecoinUtils.buildPsbtToBase64(tx);
|
|
69
|
+
}).toThrow();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should throw error for invalid address format", () => {
|
|
73
|
+
const validTxId = "a".repeat(64); // Valid 32-byte hex
|
|
74
|
+
const tx = {
|
|
75
|
+
address: "invalid-address",
|
|
76
|
+
inputs: [
|
|
77
|
+
{
|
|
78
|
+
txId: validTxId,
|
|
79
|
+
vOut: 0,
|
|
80
|
+
amount: 100000000,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
outputs: [
|
|
84
|
+
{
|
|
85
|
+
address: "invalid-recipient",
|
|
86
|
+
amount: 50000000,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Should throw error due to invalid address format
|
|
92
|
+
expect(() => {
|
|
93
|
+
DogecoinUtils.buildPsbtToBase64(tx);
|
|
94
|
+
}).toThrow();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should include nonWitnessUtxo in input options when provided (covers line 61-62)", () => {
|
|
98
|
+
const validTxId = "b".repeat(64);
|
|
99
|
+
const minimalPrevTxHex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100f2052a010000001976a914000000000000000000000000000000000000000088ac00000000";
|
|
100
|
+
const tx = {
|
|
101
|
+
address: "DTest123456789012345678901234567890",
|
|
102
|
+
inputs: [
|
|
103
|
+
{
|
|
104
|
+
txId: validTxId,
|
|
105
|
+
vOut: 0,
|
|
106
|
+
amount: 100000000,
|
|
107
|
+
nonWitnessUtxo: minimalPrevTxHex,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
outputs: [{ address: "DRecipient123456789012345678901234567890", amount: 50000000 }],
|
|
111
|
+
};
|
|
112
|
+
// nonWitnessUtxo branch (line 61-62) runs first; addOutput then throws due to invalid address
|
|
113
|
+
expect(() => DogecoinUtils.buildPsbtToBase64(tx)).toThrow(/no matching Script/);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("extractPsbtTransaction", () => {
|
|
118
|
+
it("should throw error for invalid PSBT format", () => {
|
|
119
|
+
const invalidPsbt = "invalid-psbt";
|
|
120
|
+
expect(() => {
|
|
121
|
+
DogecoinUtils.extractPsbtTransaction(invalidPsbt);
|
|
122
|
+
}).toThrow();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should use custom _maximumFeeRate when provided", () => {
|
|
126
|
+
const invalidPsbt = "invalid-psbt";
|
|
127
|
+
expect(() => {
|
|
128
|
+
DogecoinUtils.extractPsbtTransaction(invalidPsbt, 50000);
|
|
129
|
+
}).toThrow();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should call setMaximumFeeRate with _maximumFeeRate when provided (covers lines 80-81)", () => {
|
|
133
|
+
const minimalHex = "c000";
|
|
134
|
+
const base64Psbt = Buffer.from(minimalHex, "hex").toString("base64");
|
|
135
|
+
const setMaximumFeeRate = vi.fn();
|
|
136
|
+
const extractTransaction = vi.fn().mockReturnValue({ toHex: () => "deadbeef" });
|
|
137
|
+
const fromHexSpy = vi.spyOn(Psbt, "fromHex").mockReturnValue({
|
|
138
|
+
setMaximumFeeRate,
|
|
139
|
+
finalizeAllInputs: vi.fn(),
|
|
140
|
+
extractTransaction,
|
|
141
|
+
} as any);
|
|
142
|
+
|
|
143
|
+
const result = DogecoinUtils.extractPsbtTransaction(base64Psbt, 50000);
|
|
144
|
+
|
|
145
|
+
expect(setMaximumFeeRate).toHaveBeenCalledWith(50000);
|
|
146
|
+
expect(result).toBe("deadbeef");
|
|
147
|
+
fromHexSpy.mockRestore();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should use default maximumFeeRate 100000 when _maximumFeeRate not provided (covers lines 80-81)", () => {
|
|
151
|
+
const minimalHex = "c000";
|
|
152
|
+
const base64Psbt = Buffer.from(minimalHex, "hex").toString("base64");
|
|
153
|
+
const setMaximumFeeRate = vi.fn();
|
|
154
|
+
const fromHexSpy = vi.spyOn(Psbt, "fromHex").mockReturnValue({
|
|
155
|
+
setMaximumFeeRate,
|
|
156
|
+
finalizeAllInputs: vi.fn(),
|
|
157
|
+
extractTransaction: vi.fn().mockReturnValue({ toHex: () => "ab" }),
|
|
158
|
+
} as any);
|
|
159
|
+
|
|
160
|
+
DogecoinUtils.extractPsbtTransaction(base64Psbt);
|
|
161
|
+
|
|
162
|
+
expect(setMaximumFeeRate).toHaveBeenCalledWith(100000);
|
|
163
|
+
fromHexSpy.mockRestore();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("rawTxToPsbtBase64", () => {
|
|
168
|
+
it("should throw error for invalid raw transaction", async () => {
|
|
169
|
+
const invalidRawTx = "invalid-tx";
|
|
170
|
+
vi.spyOn(rpc, "getTxDetail").mockResolvedValue({
|
|
171
|
+
hex: "01000000",
|
|
172
|
+
} as any);
|
|
173
|
+
|
|
174
|
+
await expect(DogecoinUtils.rawTxToPsbtBase64(invalidRawTx)).rejects.toThrow();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should handle raw transaction with 0x prefix", async () => {
|
|
178
|
+
const rawTx =
|
|
179
|
+
"0x01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52daaf87de6c36dfd4115338ef46e897963355484b448fd40aadcbee1d4047e10ac00000000";
|
|
180
|
+
const validTxId = "a".repeat(64);
|
|
181
|
+
const mockTxDetail = {
|
|
182
|
+
hex: "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52daaf87de6c36dfd4115338ef46e897963355484b448fd40aadcbee1d4047e10ac00000000",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
vi.spyOn(rpc, "getTxDetail").mockResolvedValue(mockTxDetail as any);
|
|
186
|
+
|
|
187
|
+
// This will fail because the transaction format is invalid, but we're testing the 0x prefix handling
|
|
188
|
+
await expect(DogecoinUtils.rawTxToPsbtBase64(rawTx)).rejects.toThrow();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should throw error when previous transaction not found", async () => {
|
|
192
|
+
const rawTx =
|
|
193
|
+
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52daaf87de6c36dfd4115338ef46e897963355484b448fd40aadcbee1d4047e10ac00000000";
|
|
194
|
+
|
|
195
|
+
vi.spyOn(rpc, "getTxDetail").mockRejectedValue(new Error("Transaction not found"));
|
|
196
|
+
|
|
197
|
+
await expect(DogecoinUtils.rawTxToPsbtBase64(rawTx)).rejects.toThrow();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should throw error when previous transaction has no hex", async () => {
|
|
201
|
+
const rawTx =
|
|
202
|
+
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020602ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52daaf87de6c36dfd4115338ef46e897963355484b448fd40aadcbee1d4047e10ac00000000";
|
|
203
|
+
const mockTxDetail = {
|
|
204
|
+
txid: "test-tx",
|
|
205
|
+
// no hex property
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
vi.spyOn(rpc, "getTxDetail").mockResolvedValue(mockTxDetail as any);
|
|
209
|
+
|
|
210
|
+
await expect(DogecoinUtils.rawTxToPsbtBase64(rawTx)).rejects.toThrow();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should return PSBT base64 when rawTx is valid and getTxDetail returns hex (covers 132-183)", async () => {
|
|
214
|
+
const minimalPrevTxHex =
|
|
215
|
+
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100f2052a010000001976a914000000000000000000000000000000000000000088ac00000000";
|
|
216
|
+
const mockTx = {
|
|
217
|
+
ins: [{ hash: Buffer.alloc(32, 0), index: 0 }],
|
|
218
|
+
outs: [
|
|
219
|
+
{ script: Buffer.from([0x6a, 0x00]), value: 0 }, // OP_RETURN so fromOutputScript throws -> catch adds output with script
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
vi.spyOn(Transaction, "fromHex").mockReturnValue(mockTx as any);
|
|
223
|
+
vi.mocked(rpc.getTxDetail).mockResolvedValue({ hex: minimalPrevTxHex } as any);
|
|
224
|
+
|
|
225
|
+
const result = await DogecoinUtils.rawTxToPsbtBase64("01000000");
|
|
226
|
+
|
|
227
|
+
expect(typeof result).toBe("string");
|
|
228
|
+
expect(result.length).toBeGreaterThan(0);
|
|
229
|
+
vi.restoreAllMocks();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should throw when prevTxDetail has no hex (covers 151-152)", async () => {
|
|
233
|
+
const mockTx = {
|
|
234
|
+
ins: [{ hash: Buffer.alloc(32, 0), index: 0 }],
|
|
235
|
+
outs: [{ script: Buffer.from([0x76, 0xa9, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0xac]), value: 1000 }],
|
|
236
|
+
};
|
|
237
|
+
vi.spyOn(Transaction, "fromHex").mockReturnValue(mockTx as any);
|
|
238
|
+
vi.mocked(rpc.getTxDetail).mockResolvedValue({ txid: "tx" } as any);
|
|
239
|
+
|
|
240
|
+
await expect(DogecoinUtils.rawTxToPsbtBase64("00")).rejects.toThrow(/has no hex data/);
|
|
241
|
+
vi.restoreAllMocks();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|