@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.
Files changed (51) hide show
  1. package/dist/index.cjs +42 -24
  2. package/dist/index.d.cts +3 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +43 -25
  5. package/package.json +3 -2
  6. package/project.json +1 -1
  7. package/src/__tests__/config.test.ts +46 -0
  8. package/src/__tests__/dogecoin-utils.test.ts +147 -0
  9. package/src/__tests__/evm-utils.test.ts +133 -0
  10. package/src/__tests__/index.test.ts +40 -0
  11. package/src/__tests__/services.test.ts +285 -0
  12. package/src/__tests__/solana-utils.test.ts +131 -0
  13. package/src/__tests__/utils.test.ts +52 -0
  14. package/src/__tests__/wallet.test.ts +350 -0
  15. package/src/api/__tests__/base.test.ts +146 -0
  16. package/src/api/__tests__/index.test.ts +51 -0
  17. package/src/api/__tests__/network.test.ts +153 -0
  18. package/src/api/__tests__/token.test.ts +231 -2
  19. package/src/api/__tests__/transaction.test.ts +121 -6
  20. package/src/api/__tests__/user.test.ts +237 -3
  21. package/src/api/__tests__/wallet.test.ts +174 -4
  22. package/src/api/network.ts +9 -1
  23. package/src/api/utils/__tests__/index.test.ts +91 -0
  24. package/src/api/utils/__tests__/signature.test.ts +124 -0
  25. package/src/api/utils/index.ts +6 -2
  26. package/src/base/__tests__/network.test.ts +119 -0
  27. package/src/base/__tests__/service.test.ts +68 -0
  28. package/src/base/__tests__/token.test.ts +123 -0
  29. package/src/base/__tests__/transaction.test.ts +210 -0
  30. package/src/config.ts +2 -1
  31. package/src/dogecoin/__tests__/base.test.ts +76 -0
  32. package/src/dogecoin/__tests__/rpc.test.ts +465 -0
  33. package/src/dogecoin/__tests__/service-extended.test.ts +420 -0
  34. package/src/dogecoin/__tests__/utils-doge.test.ts +244 -0
  35. package/src/dogecoin/__tests__/utils-extended.test.ts +323 -0
  36. package/src/dogecoin/base.ts +1 -0
  37. package/src/dogecoin/config.ts +2 -2
  38. package/src/dogecoin/rpc.ts +10 -1
  39. package/src/dogecoin/service.ts +9 -5
  40. package/src/evm/__tests__/rpc.test.ts +132 -0
  41. package/src/evm/__tests__/service.test.ts +535 -0
  42. package/src/evm/__tests__/utils.test.ts +170 -0
  43. package/src/evm/service.ts +11 -0
  44. package/src/evm/utils.ts +2 -2
  45. package/src/solana/__tests__/service.test.ts +425 -0
  46. package/src/solana/__tests__/utils.test.ts +937 -0
  47. package/src/solana/config.ts +1 -1
  48. package/src/solana/service.ts +2 -0
  49. package/src/solana/utils.ts +2 -16
  50. package/src/utils/index.ts +1 -1
  51. 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
+ });
@@ -35,5 +35,6 @@ export function fromBase64(base64: string): Uint8Array {
35
35
  }
36
36
 
37
37
  export function toBase58(data: Uint8Array | Buffer | string): string {
38
+ console.warn("toBase58:", data);
38
39
  throw new Error("toBase58 requires bs58 package. Install it: pnpm add bs58");
39
40
  }
@@ -1,6 +1,6 @@
1
1
  import { ChainTypeEnum, SupportedChainTypes } from "@tomo-inc/wallet-utils";
2
2
 
3
- export const BaseConfig = SupportedChainTypes[ChainTypeEnum.DOGE] as any;
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-pro.tomo.inc/rpc/v1/doge_coin";
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
 
@@ -1,6 +1,15 @@
1
1
  import axios from "axios";
2
2
 
3
- import { BLOCK_CONFIRMATIONS, FEE_RATE_KB, MYDOGE_BASE_URL, RPC_TIMEOUT, RPC_URL, RPC_URL_TOMO, TRANSACTION_PAGE_SIZE, TX_SIZE } from "./config";
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
 
@@ -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
+ });