@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,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
|
+
});
|