@tomo-inc/chains-service 0.0.2

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 (52) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +15 -0
  3. package/package.json +38 -0
  4. package/project.json +59 -0
  5. package/src/api/__tests__/config.ts +21 -0
  6. package/src/api/__tests__/token.test.ts +120 -0
  7. package/src/api/__tests__/transaction.test.ts +86 -0
  8. package/src/api/__tests__/user.test.ts +105 -0
  9. package/src/api/__tests__/wallet.test.ts +73 -0
  10. package/src/api/base.ts +52 -0
  11. package/src/api/index.ts +24 -0
  12. package/src/api/network-data.ts +572 -0
  13. package/src/api/network.ts +81 -0
  14. package/src/api/token.ts +182 -0
  15. package/src/api/transaction.ts +59 -0
  16. package/src/api/types/common.ts +35 -0
  17. package/src/api/types/index.ts +13 -0
  18. package/src/api/types/type.ts +283 -0
  19. package/src/api/user.ts +83 -0
  20. package/src/api/utils/index.ts +34 -0
  21. package/src/api/utils/signature.ts +60 -0
  22. package/src/api/wallet.ts +57 -0
  23. package/src/base/network.ts +55 -0
  24. package/src/base/service.ts +33 -0
  25. package/src/base/token.ts +43 -0
  26. package/src/base/transaction.ts +58 -0
  27. package/src/config.ts +21 -0
  28. package/src/dogecoin/base.ts +39 -0
  29. package/src/dogecoin/config.ts +43 -0
  30. package/src/dogecoin/rpc.ts +449 -0
  31. package/src/dogecoin/service.ts +451 -0
  32. package/src/dogecoin/type.ts +29 -0
  33. package/src/dogecoin/utils-doge.ts +105 -0
  34. package/src/dogecoin/utils.ts +601 -0
  35. package/src/evm/rpc.ts +68 -0
  36. package/src/evm/service.ts +403 -0
  37. package/src/evm/utils.ts +92 -0
  38. package/src/index.ts +28 -0
  39. package/src/solana/config.ts +5 -0
  40. package/src/solana/service.ts +312 -0
  41. package/src/solana/types.ts +91 -0
  42. package/src/solana/utils.ts +635 -0
  43. package/src/types/account.ts +58 -0
  44. package/src/types/dapp.ts +7 -0
  45. package/src/types/gas.ts +53 -0
  46. package/src/types/index.ts +81 -0
  47. package/src/types/network.ts +66 -0
  48. package/src/types/tx.ts +181 -0
  49. package/src/types/wallet.ts +49 -0
  50. package/src/wallet.ts +96 -0
  51. package/tsconfig.json +14 -0
  52. package/tsup.config.ts +18 -0
@@ -0,0 +1,403 @@
1
+ import { ChainTypes, SupportedChainTypes } from "@tomo-inc/wallet-utils";
2
+ import { BigNumber } from "bignumber.js";
3
+ import {
4
+ createPublicClient,
5
+ formatUnits,
6
+ fromHex,
7
+ http,
8
+ isHex,
9
+ parseTransaction,
10
+ parseUnits,
11
+ toHex,
12
+ Transaction,
13
+ } from "viem";
14
+
15
+ import { BaseService } from "../base/service";
16
+ import { getRPCClient } from "./rpc";
17
+
18
+ import {
19
+ QueryGasParams,
20
+ ITypedData,
21
+ INetwork,
22
+ TomoAppInfo,
23
+ IaccountInfo,
24
+ TransactionParams,
25
+ QueryGasResponse,
26
+ } from "../types";
27
+ import { createErc20TxData, getAllTypeChainIds, isEvmChain } from "./utils";
28
+
29
+ export class EvmService extends BaseService {
30
+ private static instance: EvmService;
31
+ public chainType: ChainTypes | "";
32
+
33
+ public constructor(chainType: ChainTypes, accountInfo: IaccountInfo, tomoAppInfo: TomoAppInfo) {
34
+ super(tomoAppInfo, accountInfo);
35
+ this.chainType = chainType;
36
+ }
37
+
38
+ public static getInstance(chainType: ChainTypes, accountInfo: any, tomoAppInfo: TomoAppInfo) {
39
+ if (!EvmService.instance) {
40
+ EvmService.instance = new EvmService(chainType, accountInfo, tomoAppInfo);
41
+ }
42
+ return EvmService.instance;
43
+ }
44
+
45
+ public async eth_requestAccounts(): Promise<string[]> {
46
+ const res = await this.eth_accounts();
47
+ return res;
48
+ }
49
+
50
+ public async eth_accounts(): Promise<string[]> {
51
+ const accounts = await this.accountInfo.getCurrent();
52
+ return accounts.map((account: any) => account.address);
53
+ }
54
+
55
+ public async eth_chainId(): Promise<`0x${string}`> {
56
+ const chainType = this.chainType;
57
+
58
+ const currentNetwork: any = await this.getCurrentChain();
59
+ const { chainIdHex } = getAllTypeChainIds({
60
+ chainId: currentNetwork?.chainId,
61
+ chainType,
62
+ });
63
+ return chainIdHex;
64
+ }
65
+
66
+ private async getCurrentChain(): Promise<INetwork> {
67
+ this.networks.setChainType(this.chainType as ChainTypes);
68
+ const currentNetwork = await this.networks.getCurrentNetwork();
69
+ return currentNetwork;
70
+ }
71
+
72
+ public async wallet_switchEthereumChain(networks: any[]): Promise<boolean> {
73
+ const { chainId } = networks[0];
74
+ const isSupported = await this.isChainSupported(chainId);
75
+ if (!isSupported) {
76
+ throw new Error(`Chain ${chainId} is not supported`);
77
+ }
78
+ return await this.networks.setCurrentNetwork(chainId);
79
+ }
80
+
81
+ //evm chains
82
+ private async isChainSupported(chainId: string): Promise<boolean> {
83
+ const chainType = this.chainType;
84
+ const res = getAllTypeChainIds({ chainId, chainType }) as {
85
+ chainId: string;
86
+ };
87
+ if (!res.chainId) {
88
+ return false;
89
+ }
90
+ const chainInfo = await this.networks.getNetworkByChainId(res.chainId);
91
+ if (!chainInfo) {
92
+ return false;
93
+ }
94
+ return isEvmChain(chainInfo);
95
+ }
96
+
97
+ public async personal_sign([message, address]: [`0x${string}`, `0x${string}`]): Promise<`0x${string}`> {
98
+ const accounts = await this.accountInfo.getCurrent();
99
+ if (accounts[0].address !== address) {
100
+ throw new Error("address is not the current account");
101
+ }
102
+ const signature = await this.accountInfo.signMessage(message);
103
+ return signature;
104
+ }
105
+
106
+ public async eth_signTypedData_v4([address, typedData]: [`0x${string}`, ITypedData]): Promise<`0x${string}`> {
107
+ const accounts = await this.accountInfo.getCurrent();
108
+ if (accounts[0].address !== address) {
109
+ throw new Error("address is not the current account");
110
+ }
111
+ const signature = await this.accountInfo.signTypedData(typedData);
112
+
113
+ return signature;
114
+ }
115
+
116
+ public async eth_getBalance([address, type]: [`0x${string}`, "latest"]): Promise<string> {
117
+ const accounts = await this.accountInfo.getCurrent();
118
+ if (accounts[0].address !== address) {
119
+ throw new Error("address is not the current account");
120
+ }
121
+ if (type !== "latest") {
122
+ throw new Error("type is not supported");
123
+ }
124
+
125
+ const chainInfo = await this.getCurrentChain();
126
+ const { rpcClient }: { rpcClient: any } = getRPCClient(chainInfo);
127
+ try {
128
+ const balance = await rpcClient.getBalance({ address });
129
+ return balance?.toString() || "0";
130
+ } catch (error) {
131
+ console.error(error);
132
+ return "0";
133
+ }
134
+ }
135
+
136
+ public async _queryGasInfo(txData: TransactionParams): Promise<QueryGasResponse> {
137
+ const { tokenAddress, chainId, amount = 0, decimals = 9 } = txData;
138
+ const value = tokenAddress ? "0x1" : toHex(parseUnits(amount.toString(), decimals));
139
+ let callData = "0x";
140
+ if (tokenAddress) {
141
+ callData = createErc20TxData(txData, { decimals, address: tokenAddress })?.data;
142
+ }
143
+ const gasLimitParam: any = {
144
+ from: txData.from,
145
+ to: txData.to,
146
+ value,
147
+ };
148
+ const queryGasParams: QueryGasParams = {
149
+ chainIndex: Number(chainId),
150
+ callData,
151
+ gasLimitParam,
152
+ addressList: [txData.from],
153
+ };
154
+
155
+ const {
156
+ data: gasInfo,
157
+ success,
158
+ message,
159
+ }: any = await this.transactions.queryGasInfo({
160
+ chainType: this.chainType,
161
+ params: queryGasParams,
162
+ });
163
+ if (!success) {
164
+ console.error("queryGasInfo evm", txData, queryGasParams, message, gasInfo);
165
+
166
+ const BaseConfig = SupportedChainTypes[ChainTypes.EVM];
167
+ const { gasFee } = BaseConfig;
168
+ return {
169
+ success: true,
170
+ gasFee: gasFee.toString(),
171
+ };
172
+ }
173
+
174
+ const nativeChainId = SupportedChainTypes[this.chainType as ChainTypes].chainId as string;
175
+ const { nativeCurrency } = await this.networks.getNetworkByChainId(nativeChainId);
176
+
177
+ const { baseFee = 1, gasLimit = 0 } = gasInfo;
178
+
179
+ const baseFeeBN = new BigNumber(baseFee);
180
+ const calcFee = (feeLevel: string) => {
181
+ const fee = baseFeeBN.plus(gasInfo[feeLevel] || 0);
182
+ const gasFee = fee.times(gasLimit);
183
+ return formatUnits(BigInt(gasFee.toNumber()), nativeCurrency?.decimals || 9);
184
+ };
185
+ return {
186
+ success,
187
+ fees: {
188
+ low: calcFee("priorityFeeLow"),
189
+ medium: calcFee("priorityFeeMedium"),
190
+ high: calcFee("priorityFeeHigh"),
191
+ },
192
+ gasFee: calcFee("priorityFeeMedium"),
193
+ baseFee,
194
+ gasLimit,
195
+ priorityFee: {
196
+ low: gasInfo.priorityFeeLow,
197
+ medium: gasInfo.priorityFeeMedium,
198
+ high: gasInfo.priorityFeeHigh,
199
+ },
200
+ };
201
+ }
202
+
203
+ public async eth_estimateGas(txs: Transaction[]): Promise<`0x${string}`> {
204
+ const { from, to, value = "0x1" } = txs[0] || {};
205
+ const accounts = await this.accountInfo.getCurrent();
206
+ if (accounts[0].address !== from) {
207
+ throw new Error("address is not the current account");
208
+ }
209
+ if (!to) {
210
+ throw new Error("to is not set");
211
+ }
212
+
213
+ const chainId = txs[0]?.chainId || (await this.eth_chainId());
214
+ const queryGasParams: QueryGasParams = {
215
+ chainIndex: Number(chainId),
216
+ callData: "0x",
217
+ gasLimitParam: {
218
+ from,
219
+ to,
220
+ value: value as `0x${string}`,
221
+ },
222
+ addressList: [from],
223
+ };
224
+
225
+ const chainType = this.chainType as ChainTypes;
226
+ const {
227
+ data: gasInfo,
228
+ success,
229
+ message,
230
+ }: any = await this.transactions.queryGasInfo({
231
+ chainType,
232
+ params: queryGasParams,
233
+ });
234
+ if (!success) {
235
+ console.error("queryGasInfo evm", txs, message, gasInfo);
236
+
237
+ const { gasFee } = SupportedChainTypes[chainType];
238
+ return gasFee.toString() as `0x${string}`;
239
+ }
240
+
241
+ const res = getAllTypeChainIds({ chainId, chainType });
242
+ const { nativeCurrencyDecimals }: any = await this.networks.getNetworkByChainId(res.chainId as string);
243
+
244
+ const { baseFee = 1, gasLimit = 0 } = gasInfo;
245
+
246
+ const baseFeeBN = new BigNumber(baseFee);
247
+ const calcFee = (feeLevel: "priorityFeeLow" | "priorityFeeMedium" | "priorityFeeHigh") => {
248
+ const fee = baseFeeBN.plus(gasInfo[feeLevel] || 0);
249
+ const gasFee = fee.times(gasLimit);
250
+ return toHex(BigInt(gasFee.toNumber()), nativeCurrencyDecimals);
251
+ };
252
+ return calcFee("priorityFeeMedium");
253
+ }
254
+
255
+ private async createPublicClient({ chainId, rpcUrl }: { chainId?: string; rpcUrl?: string }): Promise<any> {
256
+ if (rpcUrl) {
257
+ return createPublicClient({
258
+ transport: http(rpcUrl),
259
+ });
260
+ }
261
+ if (chainId) {
262
+ this.networks.setChainType(this.chainType as ChainTypes);
263
+ const chainInfo: any = await this.networks.getNetworkByChainId(chainId);
264
+ rpcUrl = chainInfo.rpcUrls[0];
265
+ } else {
266
+ const chainInfo: any = await this.getCurrentChain();
267
+ rpcUrl = chainInfo.rpcUrls[0];
268
+ }
269
+ if (!rpcUrl) {
270
+ throw new Error("rpcUrl is not set");
271
+ }
272
+ return createPublicClient({
273
+ transport: http(rpcUrl),
274
+ });
275
+ }
276
+
277
+ // Get nonce for current account
278
+ public async eth_getTransactionCount([address, type]: [`0x${string}`, "latest" | "pending"]): Promise<number> {
279
+ const accounts = await this.accountInfo.getCurrent();
280
+ if (accounts[0].address !== address) {
281
+ throw new Error("address is not the current account");
282
+ }
283
+ if (type !== "latest" && type !== "pending") {
284
+ throw new Error("type is not supported");
285
+ }
286
+
287
+ try {
288
+ const rpcClient = await this.createPublicClient({});
289
+ const nonce = await rpcClient.getTransactionCount({ address });
290
+ return nonce;
291
+ } catch (error) {
292
+ console.error("Failed to get nonce:", error);
293
+ throw new Error(`Failed to get nonce: ${error}`);
294
+ }
295
+ }
296
+
297
+ //https://docs.metamask.io/snaps/reference/keyring-api/chain-methods/#eth_signtransaction
298
+ public async eth_signTransaction(
299
+ txParams: {
300
+ type?: number;
301
+ from: `0x${string}`;
302
+ to: `0x${string}`;
303
+ value: `0x${string}`;
304
+ data?: `0x${string}`;
305
+ chainId: `0x${string}`;
306
+ nonce: `0x${string}`;
307
+ gasLimit: `0x${string}`;
308
+ maxFeePerGas?: `0x${string}`;
309
+ maxPriorityFeePerGas?: `0x${string}`;
310
+ accessList?: any[];
311
+ }[],
312
+ ): Promise<Transaction> {
313
+ const preparedTxPropsType: any = {
314
+ chainId: "number",
315
+ to: "string",
316
+ value: "bigint",
317
+ data: "string",
318
+ nonce: "number",
319
+ maxPriorityFeePerGas: "bigint",
320
+ maxFeePerGas: "bigint",
321
+ gasLimit: "bigint",
322
+ gas: "bigint",
323
+ };
324
+ let txData: any = txParams?.[0];
325
+ const gas = txData?.gas || txData?.gasLimit;
326
+ txData = { ...txData, gas, gasLimit: gas };
327
+ txData.data = txData.data || "0x";
328
+
329
+ const accounts = await this.accountInfo.getCurrent();
330
+ const address = accounts[0].address;
331
+ if (txData?.from && txData?.from !== address) {
332
+ console.error("eth_signTransaction error data: from is not the current account", txData);
333
+ throw new Error(`eth_signTransaction error data: from is not the current account`);
334
+ }
335
+ delete txData?.from;
336
+
337
+ const preparedTx: any = {
338
+ type: "eip1559",
339
+ gas: txData?.gasLimit,
340
+ account: address,
341
+ };
342
+ for (const key in preparedTxPropsType) {
343
+ if (!txData?.[key] || !isHex(txData?.[key])) {
344
+ throw new Error(`${key}: no data or wrong type`);
345
+ }
346
+ preparedTx[key] = preparedTxPropsType[key] === "number" ? fromHex(txData[key], "number") : txData[key];
347
+ }
348
+
349
+ try {
350
+ const serializedTransaction = await this.accountInfo.signTransaction(
351
+ JSON.stringify({
352
+ data: preparedTx,
353
+ types: preparedTxPropsType,
354
+ }),
355
+ );
356
+
357
+ if (serializedTransaction === "") {
358
+ throw new Error(`eth_signTransaction error data: ${JSON.stringify(txParams)}`);
359
+ }
360
+
361
+ const data = parseTransaction(serializedTransaction as `0x${string}`) as Transaction;
362
+ // console.log("v, r, s", data, serializedTransaction);
363
+
364
+ return data;
365
+ } catch (err) {
366
+ throw new Error("eth_signTransaction error" + err);
367
+ }
368
+ }
369
+
370
+ public async eth_sendRawTransaction(
371
+ [serializedTransaction]: [`0x${string}`],
372
+ rpcUrl?: `https://${string}`,
373
+ ): Promise<`0x${string}`> {
374
+ try {
375
+ const rpcClient = await this.createPublicClient({ rpcUrl });
376
+ const hash = await rpcClient.sendRawTransaction({
377
+ serializedTransaction,
378
+ });
379
+
380
+ return hash;
381
+ } catch (error) {
382
+ console.error("Failed to send transaction via RPC:", error, rpcUrl);
383
+ throw error;
384
+ }
385
+ }
386
+
387
+ public async getTransaction(hash: `0x${string}`): Promise<Transaction> {
388
+ if (!hash || !isHex(hash)) {
389
+ throw new Error("txId is not valid");
390
+ }
391
+ try {
392
+ const rpcClient = await this.createPublicClient({});
393
+ const tx: Transaction = await rpcClient.getTransaction({
394
+ hash,
395
+ });
396
+
397
+ return tx;
398
+ } catch (error) {
399
+ console.error("Failed to send transaction via RPC:", error, hash);
400
+ throw error;
401
+ }
402
+ }
403
+ }
@@ -0,0 +1,92 @@
1
+ import {
2
+ decodeFunctionData,
3
+ encodeFunctionData,
4
+ erc20Abi,
5
+ formatEther,
6
+ fromHex,
7
+ hexToBigInt,
8
+ isAddressEqual,
9
+ isHex,
10
+ parseUnits,
11
+ toHex,
12
+ } from "viem";
13
+ import { CenterSubmitParams, SubmitParamsType } from "../types";
14
+
15
+ export const isEvmChain = (network: any) => {
16
+ return network && network?.platformType === "EVM";
17
+ };
18
+
19
+ export const isHexChainId = (chainIdHex: string) => {
20
+ return isHex(chainIdHex);
21
+ };
22
+
23
+ export const isSameAddress = (addr1: "0x${string}", addr2: "0x${string}") => {
24
+ return isAddressEqual(addr1, addr2);
25
+ };
26
+
27
+ export const getAllTypeChainIds = ({ chainId, chainType }: { chainId: string | number; chainType: string }) => {
28
+ if (isHex(chainId)) {
29
+ const chainIdHex = chainId;
30
+ chainId = fromHex(chainId, "number").toString();
31
+ return {
32
+ chainId,
33
+ chainIdHex,
34
+ chainUid: `${chainType}:${chainId}`,
35
+ };
36
+ }
37
+ const chainIdHex = toHex(Number(chainId));
38
+ return {
39
+ chainId,
40
+ chainIdHex,
41
+ chainUid: `${chainType}:${chainId}`,
42
+ };
43
+ };
44
+
45
+ export const getAmount = (value: `0x${string}`) => {
46
+ const amount = hexToBigInt(value);
47
+ return Number(formatEther(amount));
48
+ };
49
+
50
+ export async function buildSubmitTxParams(params: any): Promise<CenterSubmitParams> {
51
+ const { txParams, network, tx, serializedTransaction, from, to, tokenAddress } = params;
52
+ return {
53
+ type: params.submitType || SubmitParamsType.TRANSFER,
54
+ userId: "",
55
+ fromTokenPrice: 0,
56
+ toTokenPrice: 0,
57
+ fromAmount: params?.amount?.toString() || "0",
58
+ toAmount: params?.amount?.toString() || "0",
59
+ source: "",
60
+ slippage: 0,
61
+ fromAddress: from,
62
+ toAddress: to,
63
+ fromChainIndex: network.chainIndex?.toString(),
64
+ fromTokenAddress: tokenAddress,
65
+ toChainIndex: network.chainIndex?.toString(),
66
+ toTokenAddress: tokenAddress,
67
+ sourceDex: "",
68
+ estimatedGas: txParams.maxFeePerGas || txParams.gasLimit,
69
+ failReason: "",
70
+ tx,
71
+ callData: serializedTransaction,
72
+ };
73
+ }
74
+
75
+ export function decodeErc20Func(data: `0x${string}`) {
76
+ return decodeFunctionData({ data, abi: erc20Abi });
77
+ }
78
+
79
+ export function createErc20TxData(params: any, token: any) {
80
+ const { decimals, address: tokenAddress } = token;
81
+ const value = parseUnits(params?.amount.toString(), decimals);
82
+ const callData = encodeFunctionData({
83
+ abi: erc20Abi,
84
+ functionName: "transfer",
85
+ args: [params.to, value],
86
+ });
87
+
88
+ return {
89
+ ...params,
90
+ ...{ amount: 0, data: callData, to: tokenAddress },
91
+ };
92
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { EvmService } from "./evm/service";
2
+ import { SolanaService } from "./solana/service";
3
+ // import { DogecoinService } from "./dogecoin/service";
4
+ import { ChainTypes } from "@tomo-inc/wallet-utils";
5
+
6
+ export { EvmService, SolanaService };
7
+
8
+ export const ChainTypeServices = {
9
+ [ChainTypes.EVM]: EvmService,
10
+ [ChainTypes.SOL]: SolanaService,
11
+ // [ChainTypes.DOGE]: DogecoinService,
12
+ };
13
+
14
+ export { TomoWallet } from "./wallet";
15
+
16
+ // export * from "./types";
17
+ // export type * from "./types";
18
+
19
+ export type {
20
+ ChainAddress,
21
+ IAccount,
22
+ TomoAppInfo,
23
+ TransactionItem,
24
+ TransactionsParams,
25
+ TransactionsResponse
26
+ } from "./types";
27
+
28
+ export { AccountType, TxTypes } from "./types";
@@ -0,0 +1,5 @@
1
+ import { ChainTypes, SupportedChainTypes } from "@tomo-inc/wallet-utils";
2
+ export const BaseConfig = SupportedChainTypes[ChainTypes.SOL] as any;
3
+
4
+ export const TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
5
+ export const MSG_PREFIX = "\xffsolana offchain";