@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,133 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ isEvmChain,
4
+ isHexChainId,
5
+ isSameAddress,
6
+ getAllTypeChainIds,
7
+ getAmount,
8
+ decodeErc20Func,
9
+ createErc20TxData,
10
+ } from "../evm/utils";
11
+
12
+ describe("evm utils", () => {
13
+ describe("isEvmChain", () => {
14
+ it("should return true for EVM chain", () => {
15
+ const network = { platformType: "EVM" };
16
+ expect(isEvmChain(network)).toBe(true);
17
+ });
18
+
19
+ it("should return false for non-EVM chain", () => {
20
+ const network = { platformType: "SOLANA" };
21
+ expect(isEvmChain(network)).toBe(false);
22
+ });
23
+
24
+ it("should return false for null network", () => {
25
+ expect(isEvmChain(null)).toBe(false);
26
+ });
27
+
28
+ it("should return false for undefined network", () => {
29
+ expect(isEvmChain(undefined)).toBe(false);
30
+ });
31
+ });
32
+
33
+ describe("isHexChainId", () => {
34
+ it("should return true for valid hex chain ID", () => {
35
+ expect(isHexChainId("0x1")).toBe(true);
36
+ expect(isHexChainId("0x01")).toBe(true);
37
+ expect(isHexChainId("0x123abc")).toBe(true);
38
+ });
39
+
40
+ it("should return false for invalid hex chain ID", () => {
41
+ expect(isHexChainId("1")).toBe(false);
42
+ expect(isHexChainId("0xgh")).toBe(false);
43
+ expect(isHexChainId("")).toBe(false);
44
+ });
45
+ });
46
+
47
+ describe("isSameAddress", () => {
48
+ it("should return true for same addresses", () => {
49
+ const addr1 = "0x1234567890123456789012345678901234567890";
50
+ const addr2 = "0x1234567890123456789012345678901234567890";
51
+ expect(isSameAddress(addr1, addr2)).toBe(true);
52
+ expect(
53
+ isSameAddress("0xABC1234567890123456789012345678901234567", "0xabc1234567890123456789012345678901234567"),
54
+ ).toBe(true); // Case insensitive
55
+ });
56
+
57
+ it("should return false for different addresses", () => {
58
+ const addr1 = "0x1234567890123456789012345678901234567890";
59
+ const addr2 = "0x9876543210987654321098765432109876543210";
60
+ expect(isSameAddress(addr1, addr2)).toBe(false);
61
+ });
62
+ });
63
+
64
+ describe("getAllTypeChainIds", () => {
65
+ it("should convert hex chain ID to number and hex", () => {
66
+ const result = getAllTypeChainIds({ chainId: "0x1", 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 number chain ID to hex", () => {
73
+ const result = getAllTypeChainIds({ chainId: 1, chainType: "evm" });
74
+ expect(result.chainId).toBe("1");
75
+ expect(result.chainIdHex).toBe("0x1");
76
+ expect(result.chainUid).toBe("evm:1");
77
+ });
78
+
79
+ it("should convert string number chain ID to hex", () => {
80
+ const result = getAllTypeChainIds({ chainId: "56", chainType: "bsc" });
81
+ expect(result.chainId).toBe("56");
82
+ expect(result.chainIdHex).toBe("0x38");
83
+ expect(result.chainUid).toBe("bsc:56");
84
+ });
85
+ });
86
+
87
+ describe("getAmount", () => {
88
+ it("should convert hex value to ether amount", () => {
89
+ const result = getAmount("0x2386f26fc10000"); // 0.01 ETH in wei
90
+ expect(result).toBeCloseTo(0.01, 5);
91
+ });
92
+
93
+ it("should handle zero value", () => {
94
+ const result = getAmount("0x0");
95
+ expect(result).toBe(0);
96
+ });
97
+
98
+ it("should handle large values", () => {
99
+ const result = getAmount("0xde0b6b3a7640000"); // 1 ETH in wei
100
+ expect(result).toBe(1);
101
+ });
102
+ });
103
+
104
+ describe("decodeErc20Func", () => {
105
+ it("should decode ERC20 function data", () => {
106
+ // This is a transfer function call data
107
+ const transferData =
108
+ "0xa9059cbb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002";
109
+ const result = decodeErc20Func(transferData as `0x${string}`);
110
+ expect(result.functionName).toBe("transfer");
111
+ });
112
+ });
113
+
114
+ describe("createErc20TxData", () => {
115
+ it("should create ERC20 transfer transaction data", () => {
116
+ const params = {
117
+ to: "0x1234567890123456789012345678901234567890",
118
+ amount: "100",
119
+ };
120
+ const token = {
121
+ address: "0xTokenAddress",
122
+ decimals: 18,
123
+ };
124
+
125
+ const result = createErc20TxData(params, token);
126
+
127
+ expect(result.to).toBe("0xTokenAddress");
128
+ expect(result.amount).toBe(0);
129
+ expect(result.data).toBeDefined();
130
+ expect(result.data).toMatch(/^0xa9059cbb/); // transfer function selector
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ DogecoinService,
4
+ EvmService,
5
+ SolanaService,
6
+ ChainServiceMap,
7
+ TomoWallet,
8
+ AccountType,
9
+ TxTypes,
10
+ } from "../index";
11
+ import { ChainTypeEnum } from "@tomo-inc/wallet-utils";
12
+
13
+ describe("chains-service index", () => {
14
+ describe("exports", () => {
15
+ it("should export all services", () => {
16
+ expect(DogecoinService).toBeDefined();
17
+ expect(EvmService).toBeDefined();
18
+ expect(SolanaService).toBeDefined();
19
+ });
20
+
21
+ it("should export ChainServiceMap", () => {
22
+ expect(ChainServiceMap).toBeDefined();
23
+ expect(ChainServiceMap[ChainTypeEnum.EVM]).toBe(EvmService);
24
+ expect(ChainServiceMap[ChainTypeEnum.SOLANA]).toBe(SolanaService);
25
+ expect(ChainServiceMap[ChainTypeEnum.DOGECOIN]).toBe(DogecoinService);
26
+ });
27
+
28
+ it("should export TomoWallet", () => {
29
+ expect(TomoWallet).toBeDefined();
30
+ });
31
+
32
+ it("should export AccountType enum", () => {
33
+ expect(AccountType).toBeDefined();
34
+ });
35
+
36
+ it("should export TxTypes enum", () => {
37
+ expect(TxTypes).toBeDefined();
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,285 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { EvmService } from "../evm/service";
3
+ import { DogecoinService } from "../dogecoin/service";
4
+ import { SolanaService } from "../solana/service";
5
+ import { ChainTypeEnum } from "@tomo-inc/wallet-utils";
6
+ import { IAccountInfo, TomoAppInfo } from "../types";
7
+ import * as rpcModule from "../evm/rpc";
8
+
9
+ describe("Chain Services", () => {
10
+ let mockAccountInfo: IAccountInfo;
11
+ let mockTomoAppInfo: TomoAppInfo;
12
+
13
+ beforeEach(() => {
14
+ mockAccountInfo = {
15
+ getCurrent: vi.fn().mockResolvedValue([
16
+ {
17
+ address: "0x1234567890123456789012345678901234567890",
18
+ publicKey: "test-public-key",
19
+ },
20
+ ]),
21
+ signMessage: vi.fn().mockResolvedValue("0xsignature"),
22
+ signTypedData: vi.fn().mockResolvedValue("0xtypedSignature"),
23
+ } as any;
24
+
25
+ mockTomoAppInfo = {
26
+ tomoStage: "dev",
27
+ appId: "test-app",
28
+ appName: "Test App",
29
+ };
30
+ });
31
+
32
+ describe("EvmService", () => {
33
+ it("should create instance", () => {
34
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
35
+ expect(service).toBeInstanceOf(EvmService);
36
+ expect(service.chainType).toBe(ChainTypeEnum.EVM);
37
+ });
38
+
39
+ it("should get instance using singleton pattern", () => {
40
+ const service1 = EvmService.getInstance(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
41
+ const service2 = EvmService.getInstance(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
42
+ expect(service1).toBe(service2);
43
+ });
44
+
45
+ it("should get accounts", async () => {
46
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
47
+ const accounts = await service.eth_accounts();
48
+ expect(accounts).toEqual(["0x1234567890123456789012345678901234567890"]);
49
+ expect(mockAccountInfo.getCurrent).toHaveBeenCalled();
50
+ });
51
+
52
+ it("should request accounts", async () => {
53
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
54
+ const accounts = await service.eth_requestAccounts();
55
+ expect(accounts).toEqual(["0x1234567890123456789012345678901234567890"]);
56
+ });
57
+
58
+ it("should sign message", async () => {
59
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
60
+ const signature = await service.personal_sign([
61
+ "0xmessage" as `0x${string}`,
62
+ "0x1234567890123456789012345678901234567890" as `0x${string}`,
63
+ ]);
64
+ expect(signature).toBe("0xsignature");
65
+ expect(mockAccountInfo.signMessage).toHaveBeenCalledWith("0xmessage");
66
+ });
67
+
68
+ it("should throw error when address mismatch in personal_sign", async () => {
69
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
70
+ // Use a valid Ethereum address format (40 hex characters)
71
+ const differentAddress = "0x1111111111111111111111111111111111111111" as `0x${string}`;
72
+ await expect(service.personal_sign(["0xmessage" as `0x${string}`, differentAddress])).rejects.toThrow(
73
+ "address is not the current account",
74
+ );
75
+ });
76
+
77
+ it("should sign typed data", async () => {
78
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
79
+ const typedData = {
80
+ domain: {},
81
+ types: {},
82
+ message: {},
83
+ };
84
+ const signature = await service.eth_signTypedData_v4([
85
+ "0x1234567890123456789012345678901234567890" as `0x${string}`,
86
+ typedData,
87
+ ]);
88
+ expect(signature).toBe("0xtypedSignature");
89
+ expect(mockAccountInfo.signTypedData).toHaveBeenCalledWith(typedData);
90
+ });
91
+
92
+ it("should throw error when address mismatch in eth_signTypedData_v4", async () => {
93
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
94
+ const typedData = {
95
+ domain: {},
96
+ types: {},
97
+ message: {},
98
+ };
99
+ // Use a valid Ethereum address format (40 hex characters)
100
+ const differentAddress = "0x1111111111111111111111111111111111111111" as `0x${string}`;
101
+ await expect(service.eth_signTypedData_v4([differentAddress, typedData])).rejects.toThrow(
102
+ "address is not the current account",
103
+ );
104
+ });
105
+
106
+ it("should get balance", async () => {
107
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
108
+
109
+ // Mock getCurrentChain
110
+ const mockChain = {
111
+ chainId: 1,
112
+ rpcUrls: ["https://rpc.example.com"],
113
+ nativeCurrencyName: "ETH",
114
+ nativeCurrencySymbol: "ETH",
115
+ nativeCurrencyDecimals: 18,
116
+ name: "Ethereum",
117
+ };
118
+ vi.spyOn(service as any, "getCurrentChain").mockResolvedValue(mockChain);
119
+
120
+ // Mock getRPCClient
121
+ const mockRpcClient = {
122
+ getBalance: vi.fn().mockResolvedValue(BigInt("1000000000000000000")),
123
+ };
124
+ vi.spyOn(rpcModule, "getRPCClient").mockReturnValue({
125
+ rpcClient: mockRpcClient,
126
+ } as any);
127
+
128
+ const balance = await service.eth_getBalance([
129
+ "0x1234567890123456789012345678901234567890" as `0x${string}`,
130
+ "latest",
131
+ ]);
132
+ expect(balance).toBe("1000000000000000000");
133
+ expect(mockRpcClient.getBalance).toHaveBeenCalled();
134
+ });
135
+
136
+ it("should throw error when address mismatch in eth_getBalance", async () => {
137
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
138
+ // Use a valid Ethereum address format (40 hex characters)
139
+ const differentAddress = "0x1111111111111111111111111111111111111111" as `0x${string}`;
140
+ await expect(service.eth_getBalance([differentAddress, "latest"])).rejects.toThrow(
141
+ "address is not the current account",
142
+ );
143
+ });
144
+
145
+ it("should throw error for unsupported type in eth_getBalance", async () => {
146
+ const service = new EvmService(ChainTypeEnum.EVM, mockAccountInfo, mockTomoAppInfo);
147
+ await expect(
148
+ service.eth_getBalance(["0x1234567890123456789012345678901234567890" as `0x${string}`, "pending" as any]),
149
+ ).rejects.toThrow("type is not supported");
150
+ });
151
+ });
152
+
153
+ describe("DogecoinService", () => {
154
+ it("should create instance", () => {
155
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
156
+ expect(service).toBeInstanceOf(DogecoinService);
157
+ expect(service.chainType).toBe(ChainTypeEnum.DOGECOIN);
158
+ });
159
+
160
+ it("should get instance using singleton pattern", () => {
161
+ const service1 = DogecoinService.getInstance(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
162
+ const service2 = DogecoinService.getInstance(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
163
+ expect(service1).toBe(service2);
164
+ });
165
+
166
+ it("should request accounts", async () => {
167
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
168
+ const accounts = await service.requestAccounts();
169
+ expect(accounts).toEqual(["0x1234567890123456789012345678901234567890"]);
170
+ });
171
+
172
+ it("should get accounts", async () => {
173
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
174
+ const accounts = await service.getAccounts();
175
+ expect(accounts).toEqual(["0x1234567890123456789012345678901234567890"]);
176
+ });
177
+
178
+ it("should get connection status", async () => {
179
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
180
+ const status = await service.getConnectionStatus();
181
+ expect(status.connected).toBe(true);
182
+ expect(status.address).toBe("0x1234567890123456789012345678901234567890");
183
+ expect(status.selectedWalletAddress).toBe("0x1234567890123456789012345678901234567890");
184
+ });
185
+
186
+ it("should throw error when address is not set in getConnectionStatus", async () => {
187
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
188
+ mockAccountInfo.getCurrent = vi.fn().mockResolvedValue([]);
189
+ await expect(service.getConnectionStatus()).rejects.toThrow("address is not set");
190
+ });
191
+
192
+ it("should sign message", async () => {
193
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
194
+ const signature = await service.signMessage({ message: "test message" });
195
+ expect(signature).toBe("0xsignature");
196
+ expect(mockAccountInfo.signMessage).toHaveBeenCalledWith("test message");
197
+ });
198
+
199
+ it("should throw error for unsupported message type", async () => {
200
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
201
+ await expect(service.signMessage({ message: "test", type: "bip322simple" })).rejects.toThrow(
202
+ "bip322simple not support.",
203
+ );
204
+ });
205
+
206
+ it("should request signed message", async () => {
207
+ const service = new DogecoinService(ChainTypeEnum.DOGECOIN, mockAccountInfo, mockTomoAppInfo);
208
+ const result = await service.requestSignedMessage({ message: "test message" });
209
+ expect(result.signedMessage).toBe("0xsignature");
210
+ });
211
+ });
212
+
213
+ describe("SolanaService", () => {
214
+ it("should create instance", () => {
215
+ const service = new SolanaService(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
216
+ expect(service).toBeInstanceOf(SolanaService);
217
+ expect(service.chainType).toBe(ChainTypeEnum.SOLANA);
218
+ });
219
+
220
+ it("should get instance using singleton pattern", () => {
221
+ const service1 = SolanaService.getInstance(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
222
+ const service2 = SolanaService.getInstance(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
223
+ expect(service1).toBe(service2);
224
+ });
225
+
226
+ it("should get current wallet account", async () => {
227
+ const service = new SolanaService(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
228
+ // getCurrentWalletAccount returns getCurrent() directly, which returns array
229
+ const account = await service.getCurrentWalletAccount();
230
+ expect(account).toEqual([
231
+ {
232
+ address: "0x1234567890123456789012345678901234567890",
233
+ publicKey: "test-public-key",
234
+ },
235
+ ]);
236
+ });
237
+
238
+ it("should get account", async () => {
239
+ // Reset mock for this test
240
+ mockAccountInfo.getCurrent = vi.fn().mockResolvedValue({
241
+ address: "0x1234567890123456789012345678901234567890",
242
+ publicKey: "test-public-key",
243
+ });
244
+ const service = new SolanaService(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
245
+ const account = await service.getAccount();
246
+ expect(account.publicKey).toBe("test-public-key");
247
+ expect(account.address).toBe("0x1234567890123456789012345678901234567890");
248
+ });
249
+
250
+ it("should sign message", async () => {
251
+ // Reset mock for this test
252
+ mockAccountInfo.getCurrent = vi.fn().mockResolvedValue({
253
+ address: "0x1234567890123456789012345678901234567890",
254
+ publicKey: "test-public-key",
255
+ });
256
+ const service = new SolanaService(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
257
+ const result = await service.signMessage({ message: "test message" });
258
+ expect(result.signature).toBe("0xsignature");
259
+ expect(result.message).toContain("test message");
260
+ expect(mockAccountInfo.signMessage).toHaveBeenCalledWith("test message");
261
+ });
262
+
263
+ it("should sign in", async () => {
264
+ // Reset mock for this test - signIn expects getCurrent to return an object
265
+ mockAccountInfo.getCurrent = vi.fn().mockResolvedValue({
266
+ address: "0x1234567890123456789012345678901234567890",
267
+ publicKey: "test-public-key",
268
+ });
269
+ const service = new SolanaService(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
270
+
271
+ const result = await service.signIn({ message: "test message" });
272
+ expect(result.signature).toBe("0xsignature");
273
+ expect(result.address).toBe("0x1234567890123456789012345678901234567890");
274
+ expect(result.publicKey).toBe("test-public-key");
275
+ expect(result.signedMessage).toContain("test message");
276
+ });
277
+
278
+ it("should throw error for unsupported request method", async () => {
279
+ const service = new SolanaService(ChainTypeEnum.SOLANA, mockAccountInfo, mockTomoAppInfo);
280
+ await expect(service.request({ method: "unsupportedMethod", params: {} })).rejects.toThrow(
281
+ "unsupportedMethod in request is not supported",
282
+ );
283
+ });
284
+ });
285
+ });
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isAddress, txToHex, hexToTx, hexToUint8Array, decodeMetadata } from "../solana/utils";
3
+ import { VersionedTransaction, TransactionMessage, PublicKey, Transaction } from "@solana/web3.js";
4
+
5
+ describe("solana utils", () => {
6
+ describe("isAddress", () => {
7
+ it("should return true for valid Solana address", () => {
8
+ const validAddress = "11111111111111111111111111111111";
9
+ expect(isAddress(validAddress)).toBe(true);
10
+ });
11
+
12
+ it("should return false for invalid Solana address", () => {
13
+ expect(isAddress("invalid")).toBe(false);
14
+ expect(isAddress("")).toBe(false);
15
+ });
16
+ });
17
+
18
+ describe("txToHex", () => {
19
+ it("should convert VersionedTransaction to hex", () => {
20
+ const mockTx = new VersionedTransaction(
21
+ new TransactionMessage({
22
+ payerKey: new PublicKey("11111111111111111111111111111111"),
23
+ recentBlockhash: "11111111111111111111111111111111",
24
+ instructions: [],
25
+ }).compileToV0Message(),
26
+ );
27
+
28
+ const result = txToHex(mockTx);
29
+ expect(typeof result).toBe("string");
30
+ expect(result.length).toBeGreaterThan(0);
31
+ });
32
+
33
+ it("should convert Transaction to hex", () => {
34
+ const mockTx = new Transaction();
35
+ // Set required fields for Transaction
36
+ mockTx.recentBlockhash = "11111111111111111111111111111111";
37
+ mockTx.feePayer = new PublicKey("11111111111111111111111111111111");
38
+
39
+ const result = txToHex(mockTx);
40
+ expect(typeof result).toBe("string");
41
+ expect(result.length).toBeGreaterThan(0);
42
+ });
43
+ });
44
+
45
+ describe("hexToTx", () => {
46
+ it("should convert hex to VersionedTransaction", () => {
47
+ // This would need actual valid transaction hex
48
+ // For now, we test that it attempts deserialization
49
+ expect(() => {
50
+ hexToTx("invalidhex");
51
+ }).toThrow();
52
+ });
53
+
54
+ it("should throw error for invalid hex", () => {
55
+ expect(() => {
56
+ hexToTx("invalid");
57
+ }).toThrow("Failed to deserialize transaction");
58
+ });
59
+ });
60
+
61
+ describe("hexToUint8Array", () => {
62
+ it("should convert hex string with 0x prefix to Uint8Array", () => {
63
+ const hex = "0x" + "a".repeat(128);
64
+ const result = hexToUint8Array(hex);
65
+ expect(result).toBeInstanceOf(Uint8Array);
66
+ expect(result.length).toBe(64);
67
+ });
68
+
69
+ it("should convert hex string without 0x prefix to Uint8Array", () => {
70
+ const hex = "a".repeat(128);
71
+ const result = hexToUint8Array(hex);
72
+ expect(result).toBeInstanceOf(Uint8Array);
73
+ expect(result.length).toBe(64);
74
+ });
75
+
76
+ it("should throw error for invalid signature length", () => {
77
+ expect(() => hexToUint8Array("0x123")).toThrow("Invalid signature length");
78
+ expect(() => hexToUint8Array("123")).toThrow("Invalid signature length");
79
+ });
80
+ });
81
+
82
+ describe("decodeMetadata", () => {
83
+ it("should decode metadata from buffer", () => {
84
+ // Create a mock buffer with metadata structure
85
+ const buffer = Buffer.alloc(200);
86
+ let offset = 1 + 32 + 32; // Key + UpdateAuthority + Mint
87
+
88
+ // Write name length and name
89
+ buffer.writeUInt32LE(4, offset);
90
+ offset += 4;
91
+ buffer.write("Test", offset);
92
+ offset += 4;
93
+
94
+ // Write symbol length and symbol
95
+ buffer.writeUInt32LE(3, offset);
96
+ offset += 4;
97
+ buffer.write("TST", offset);
98
+ offset += 3;
99
+
100
+ // Write URI length and URI
101
+ buffer.writeUInt32LE(10, offset);
102
+ offset += 4;
103
+ buffer.write("https://...", offset);
104
+ offset += 10;
105
+
106
+ // Write seller fee (2 bytes)
107
+ offset += 2;
108
+
109
+ // Write creator length (1 byte)
110
+ buffer[offset] = 0;
111
+ offset += 1;
112
+
113
+ // Write decimals (1 byte)
114
+ buffer[offset] = 9;
115
+
116
+ const result = decodeMetadata(buffer);
117
+ expect(result.name).toBe("Test");
118
+ expect(result.symbol).toBe("TST");
119
+ expect(result.decimals).toBe(9);
120
+ });
121
+
122
+ it("should return default values on error", () => {
123
+ const invalidBuffer = Buffer.from("invalid");
124
+ const result = decodeMetadata(invalidBuffer);
125
+ expect(result.name).toBe("");
126
+ expect(result.symbol).toBe("");
127
+ expect(result.uri).toBe("");
128
+ expect(result.decimals).toBe(0);
129
+ });
130
+ });
131
+ });
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { waitOnly } from "../utils";
3
+
4
+ describe("utils", () => {
5
+ beforeEach(() => {
6
+ vi.useFakeTimers();
7
+ });
8
+
9
+ afterEach(() => {
10
+ vi.restoreAllMocks();
11
+ vi.useRealTimers();
12
+ });
13
+
14
+ describe("waitOnly", () => {
15
+ it("should resolve after specified milliseconds", async () => {
16
+ const ms = 1000;
17
+ const promise = waitOnly(ms);
18
+
19
+ vi.advanceTimersByTime(ms);
20
+
21
+ const result = await promise;
22
+ expect(result).toBe(ms);
23
+ });
24
+
25
+ it("should resolve with correct delay", async () => {
26
+ const ms = 500;
27
+ const startTime = Date.now();
28
+ const promise = waitOnly(ms);
29
+
30
+ vi.advanceTimersByTime(ms);
31
+
32
+ await promise;
33
+ // With fake timers, we just verify it resolves
34
+ expect(await promise).toBe(ms);
35
+ });
36
+
37
+ it("should handle zero delay", async () => {
38
+ const promise = waitOnly(0);
39
+ vi.advanceTimersByTime(0);
40
+ const result = await promise;
41
+ expect(result).toBe(0);
42
+ });
43
+
44
+ it("should handle large delay", async () => {
45
+ const ms = 10000;
46
+ const promise = waitOnly(ms);
47
+ vi.advanceTimersByTime(ms);
48
+ const result = await promise;
49
+ expect(result).toBe(ms);
50
+ });
51
+ });
52
+ });