@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,601 @@
1
+ import { cache } from "@tomo-inc/wallet-utils";
2
+ import { BigNumber } from "bignumber.js";
3
+ import { DogecoinUtils } from "./utils-doge";
4
+
5
+ import * as API from "./rpc";
6
+
7
+ import { ChainTypes, SupportedChainTypes } from "@tomo-inc/wallet-utils";
8
+ import { DECIMALS, RPC_URL, network } from "./config";
9
+ import { DogeSpendableUtxos } from "./type";
10
+
11
+ import * as base from "./base";
12
+
13
+ import { Psbt } from "bitcoinjs-lib";
14
+
15
+ const KOINU_PER_DOGE = new BigNumber(DECIMALS); // 10^8
16
+
17
+ export async function waitOnly(ms: number) {
18
+ return new Promise((resolve, reject) => {
19
+ setTimeout(() => {
20
+ resolve(ms);
21
+ }, ms);
22
+ });
23
+ }
24
+
25
+ export function toSatoshi(bitcion: number | string): number {
26
+ try {
27
+ const amount = new BigNumber(bitcion);
28
+ if (amount.isNaN() || amount.isNegative()) {
29
+ throw new Error("Invalid amount");
30
+ }
31
+ return amount.times(KOINU_PER_DOGE).integerValue(BigNumber.ROUND_DOWN).toNumber();
32
+ } catch (error: any) {
33
+ throw new Error(`toSatoshi failed: ${error.message}`);
34
+ }
35
+ }
36
+
37
+ export function toBitcoin(satoshis: number | string): number {
38
+ try {
39
+ const amount = new BigNumber(satoshis);
40
+ if (amount.isNaN() || amount.isNegative()) {
41
+ throw new Error("Invalid Koinu amount");
42
+ }
43
+ return amount.dividedBy(KOINU_PER_DOGE).toNumber();
44
+ } catch (error: any) {
45
+ throw new Error(`toBitcoin failed: ${error.message}`);
46
+ }
47
+ }
48
+
49
+ export const isDogeCoin = (token: any = {}, network: any = {}) => {
50
+ return token?.address === "" && (network?.chainId === "3" || network?.chainId === "221122420");
51
+ };
52
+
53
+ export const isDogeOSDevnet = (network: any = {}) => {
54
+ return network?.chainId === "221122420";
55
+ };
56
+
57
+ export const isDogeMainnet = (network: any = {}) => {
58
+ return network?.chainId === "3";
59
+ };
60
+
61
+ export const isHasDoge = (balances: any) => {
62
+ return !!balances?.[300];
63
+ };
64
+
65
+ export const isDogeChain = (network: any = {}) => {
66
+ const { chainType, chainIndex } = network;
67
+ return chainType === ChainTypes.DOGE && chainIndex === SupportedChainTypes[ChainTypes.DOGE].chainIndex;
68
+ };
69
+
70
+ export function hexToBase64(hex: string): string {
71
+ try {
72
+ return base?.toBase64(base?.fromHex(hex));
73
+ } catch (error) {
74
+ console.error("PSBT hex to base64 failed:", error);
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ export function base64ToHex(base64: string): string {
80
+ try {
81
+ return base?.toHex(base?.fromBase64(base64));
82
+ } catch (error) {
83
+ console.error("PSBT base64 to hex failed:", error);
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ export const Drc20LargeNumber = (n: number) => {
89
+ const num = n || 0;
90
+
91
+ if (num >= 1000 && num < 1000000) {
92
+ return num.toLocaleString();
93
+ }
94
+
95
+ if (num >= 1000000) {
96
+ return formatCompactNumber(num, 1);
97
+ }
98
+
99
+ return num.toString();
100
+ };
101
+
102
+ //from mydoge
103
+ export const formatCompactNumber = (num = 0, decimals = 1) => {
104
+ const suffixes = ["", "k", "m", "billion", "trillion"];
105
+ const absNum = Math.abs(num);
106
+
107
+ if (absNum < 1000) return num.toFixed(decimals);
108
+
109
+ const exp = Math.min(Math.floor(Math.log10(absNum) / 3), suffixes.length - 1);
110
+ const shortened = num / 1000 ** exp;
111
+
112
+ return `${shortened.toLocaleString(undefined, {
113
+ minimumFractionDigits: 0,
114
+ maximumFractionDigits: decimals,
115
+ })} ${suffixes[exp]}`;
116
+ };
117
+
118
+ //from mydoge
119
+ export function formatSatoshisAsDoge(value: number, maxDecimals?: number): string {
120
+ if (value >= 1) {
121
+ const newValue = toBitcoin(Math.floor(value));
122
+ return formatDoge(newValue, maxDecimals);
123
+ } else {
124
+ return formatDoge(value / 1e8, 10);
125
+ }
126
+ }
127
+
128
+ //from mydoge
129
+ export function formatDoge(value: number | string, maxDecimals?: number, useGrouping?: any): string {
130
+ let newValue = value;
131
+ const opts: any = {};
132
+ if (maxDecimals !== undefined) {
133
+ opts.maximumFractionDigits = maxDecimals;
134
+ }
135
+ if (useGrouping !== undefined) {
136
+ opts.useGrouping = useGrouping;
137
+ }
138
+ // show 4.20 instead of 4.2
139
+ if (newValue === 4.2) {
140
+ opts.minimumFractionDigits = 2;
141
+ }
142
+ newValue = newValue.toLocaleString(undefined, opts);
143
+ return newValue;
144
+ }
145
+
146
+ export function addUsedUtxos(newUsedUtxos: object) {
147
+ const usedUtxos = cache.get("UsedUtxos") || {};
148
+ for (const txid in newUsedUtxos) {
149
+ usedUtxos[txid] = 1;
150
+ }
151
+ cache.set("UsedUtxos", usedUtxos, false);
152
+ }
153
+
154
+ export function getUsedUtxos() {
155
+ return cache.get("UsedUtxos") || {};
156
+ }
157
+
158
+ export async function createInscriptionPsbt({
159
+ inscriptionId,
160
+ senderAddress,
161
+ recipientAddress,
162
+ utxos,
163
+ fee,
164
+ amount,
165
+ location,
166
+ }: any) {
167
+ const inscription = await API.getTxDetail(inscriptionId);
168
+
169
+ let vout = inscription.vout[0]?.n;
170
+ if (location) {
171
+ const locations = location.split(":");
172
+ vout = Number(locations[1] || vout);
173
+ }
174
+
175
+ const inputs = [
176
+ {
177
+ txId: inscriptionId,
178
+ vOut: vout,
179
+ amount: toSatoshi(amount),
180
+ nonWitnessUtxo: inscription.hex,
181
+ address: senderAddress,
182
+ },
183
+ ];
184
+
185
+ const sendCost = toSatoshi(fee);
186
+
187
+ const usedUtxos = getUsedUtxos();
188
+ const sortedUtxos = utxos.sort((a: any, b: any) => b.outputValue - a.outputValue);
189
+
190
+ // First pass: collect sufficient UTXOs to cover the fee
191
+ const selectedUtxos = [];
192
+ let accumulatedAmount = 0;
193
+
194
+ for (const utxo of sortedUtxos) {
195
+ const { txid } = utxo;
196
+ if (accumulatedAmount >= sendCost || usedUtxos[txid]) continue;
197
+
198
+ selectedUtxos.push(utxo);
199
+ accumulatedAmount += Number(utxo.outputValue);
200
+ }
201
+
202
+ // Second pass: concurrently fetch UTXO details
203
+ const utxoDetailPromises = selectedUtxos.map(async (utxo) => {
204
+ try {
205
+ const utxoDetail = await API.getTxDetail(utxo.txid);
206
+ return { utxo, utxoDetail };
207
+ } catch (error) {
208
+ return { utxo, utxoDetail: null };
209
+ }
210
+ });
211
+
212
+ const utxoResults = await Promise.all(utxoDetailPromises);
213
+
214
+ // Third pass: construct inputs from successfully fetched details
215
+ const usingUtxos: any = {};
216
+ let addedAmount = 0;
217
+
218
+ for (const { utxo, utxoDetail } of utxoResults) {
219
+ if (addedAmount >= sendCost) break;
220
+
221
+ const { txid, vout, outputValue, address = senderAddress } = utxo;
222
+
223
+ if (utxoDetail?.hex && !usedUtxos[txid] && !usingUtxos[txid]) {
224
+ inputs.push({
225
+ txId: txid,
226
+ vOut: vout,
227
+ amount: Number(outputValue),
228
+ nonWitnessUtxo: utxoDetail.hex,
229
+ address,
230
+ });
231
+ usingUtxos[txid] = 1;
232
+ addedAmount += Number(outputValue);
233
+ }
234
+ }
235
+
236
+ if (addedAmount < sendCost) {
237
+ throw new Error("not enough funds to cover amount and fee");
238
+ }
239
+
240
+ const params: any = {
241
+ address: senderAddress,
242
+ inputs,
243
+ outputs: [
244
+ {
245
+ address: recipientAddress,
246
+ amount: toSatoshi(amount),
247
+ },
248
+ {
249
+ address: senderAddress,
250
+ amount: addedAmount - sendCost,
251
+ },
252
+ ],
253
+ };
254
+
255
+ const psbtBase64 = DogecoinUtils.buildPsbtToBase64(params);
256
+ return { psbtBase64, usingUtxos };
257
+ }
258
+
259
+ export async function createPsbt({
260
+ from,
261
+ to,
262
+ amount,
263
+ fee,
264
+ spendableUtxos,
265
+ }: {
266
+ from: string;
267
+ to: string;
268
+ amount: number;
269
+ fee: number;
270
+ spendableUtxos: DogeSpendableUtxos[];
271
+ }) {
272
+ let utxos = spendableUtxos;
273
+ if (!utxos || utxos.length === 0) {
274
+ utxos = await API.getSpendableUtxos(from);
275
+ }
276
+
277
+ const sendAmount = toSatoshi(amount);
278
+ const sendCost = toSatoshi(fee);
279
+ const totalNeeded = sendAmount + sendCost;
280
+
281
+ const sortedUtxos = utxos.sort((a, b) => b.outputValue - a.outputValue);
282
+
283
+ // First pass: collect sufficient UTXOs to cover the amount + fee
284
+ const selectedUtxos = [];
285
+ let accumulatedAmount = 0;
286
+
287
+ for (const utxo of sortedUtxos) {
288
+ if (accumulatedAmount >= totalNeeded) break;
289
+
290
+ selectedUtxos.push(utxo);
291
+ accumulatedAmount += Number(utxo.outputValue);
292
+ }
293
+
294
+ if (accumulatedAmount < totalNeeded) {
295
+ throw new Error("not enough funds to cover amount and fee");
296
+ }
297
+
298
+ // Second pass: concurrently fetch UTXO details
299
+ const utxoDetailPromises = selectedUtxos.map(async (utxo) => {
300
+ try {
301
+ const utxoDetail = await API.getTxDetail(utxo.txid);
302
+ return { utxo, utxoDetail };
303
+ } catch (error) {
304
+ return { utxo, utxoDetail: null };
305
+ }
306
+ });
307
+
308
+ const utxoResults = await Promise.all(utxoDetailPromises);
309
+
310
+ // Third pass: construct inputs from successfully fetched details
311
+ const inputs = [];
312
+ const usingUtxos: any = {};
313
+ let addedAmount = 0;
314
+
315
+ for (const { utxo, utxoDetail } of utxoResults) {
316
+ if (addedAmount >= totalNeeded) break;
317
+
318
+ const { txid, vout, outputValue, address = from }: any = utxo;
319
+
320
+ if (utxoDetail?.hex && !usingUtxos[txid]) {
321
+ inputs.push({
322
+ txId: txid,
323
+ vOut: vout,
324
+ amount: Number(outputValue),
325
+ nonWitnessUtxo: utxoDetail.hex,
326
+ address,
327
+ });
328
+ usingUtxos[txid] = 1;
329
+ addedAmount += Number(outputValue);
330
+ }
331
+ }
332
+
333
+ if (addedAmount < totalNeeded) {
334
+ throw new Error("not enough funds to cover amount and fee");
335
+ }
336
+
337
+ const outputs = [
338
+ {
339
+ address: to,
340
+ amount: sendAmount,
341
+ },
342
+ ];
343
+
344
+ const changeAmount = addedAmount - sendAmount - sendCost;
345
+ if (changeAmount > 0) {
346
+ outputs.push({
347
+ address: from,
348
+ amount: changeAmount,
349
+ });
350
+ }
351
+
352
+ const params = {
353
+ address: from,
354
+ inputs,
355
+ outputs,
356
+ };
357
+
358
+ const psbtBase64 = DogecoinUtils.buildPsbtToBase64(params as any);
359
+ return { psbtBase64, usingUtxos };
360
+ }
361
+
362
+ export async function createTxData(txInfo: any, senderAddress: string) {
363
+ let txData = {
364
+ ...txInfo,
365
+ ...{
366
+ sender: senderAddress,
367
+ feeRate: 1,
368
+ amountMismatch: false,
369
+ },
370
+ };
371
+
372
+ try {
373
+ const { feePerKB }: any = await API.estimateSmartFee({ senderAddress });
374
+ const { balance = 0 } = await API.getBalance(senderAddress);
375
+ let amountMismatch = false;
376
+ const balanceBN = new BigNumber(balance);
377
+ const amountBN = new BigNumber(Number(txInfo.amount)).times(DECIMALS);
378
+ if (balanceBN < amountBN.plus(feePerKB)) {
379
+ amountMismatch = true;
380
+ }
381
+
382
+ txData = {
383
+ ...txInfo,
384
+ ...{
385
+ sender: senderAddress,
386
+ feeRate: feePerKB,
387
+ amountMismatch,
388
+ },
389
+ };
390
+ } catch (err) {
391
+ console.error(err);
392
+ }
393
+ return txData;
394
+ }
395
+
396
+ export function createSocialTxData(data: any, account: any) {
397
+ const senderAddress = data.senderAddress || data.sender || data.from || data.fromAddress;
398
+ const fee = data.fee || data.feeRate || 0;
399
+ return {
400
+ from: senderAddress,
401
+ to: data.to,
402
+ amount: Number(data.amount),
403
+ fee: toSatoshi(fee),
404
+ // accountId: account?.id,
405
+ walletId: -1,
406
+ rpcUrl: data.rpcUrl || RPC_URL,
407
+ };
408
+ }
409
+
410
+ export interface DogeTxData {
411
+ amountMismatch?: boolean;
412
+ amount: number;
413
+ fee: number;
414
+ to: string;
415
+ from: string;
416
+ senderAddress?: string;
417
+ sender?: string;
418
+ fromAddress?: string;
419
+ spendableUtxos?: DogeSpendableUtxos[];
420
+ }
421
+ export async function createBtcTxData(txData: DogeTxData) {
422
+ const transferAmount = toSatoshi(txData.amount);
423
+ const feeAmount = toSatoshi(txData.fee);
424
+ const senderAddress = txData.senderAddress || txData.sender || txData.from || txData.fromAddress;
425
+
426
+ const usedUtxos = getUsedUtxos();
427
+ const usingUtxos: any = {};
428
+
429
+ let utxos = txData.spendableUtxos;
430
+ if (!utxos || utxos.length === 0) {
431
+ throw new Error("No spendable UTXOs available");
432
+ }
433
+
434
+ utxos = utxos.sort((a, b) => b.outputValue - a.outputValue);
435
+
436
+ const requiredAmount = transferAmount + feeAmount;
437
+ let total = 0;
438
+ const inputs = [];
439
+
440
+ // select enough UTXOs
441
+ for (const utxo of utxos) {
442
+ if (total >= requiredAmount) break;
443
+
444
+ if (!usedUtxos[utxo.txid]) {
445
+ inputs.push({
446
+ txId: utxo.txid,
447
+ vOut: utxo.vout,
448
+ amount: Number(utxo.outputValue),
449
+ });
450
+ usingUtxos[utxo.txid] = 1;
451
+ total += Number(utxo.outputValue);
452
+ }
453
+ }
454
+
455
+ // check balance
456
+ if (total < requiredAmount) {
457
+ throw new Error("Insufficient funds");
458
+ }
459
+
460
+ const outputs = [
461
+ {
462
+ address: txData.to,
463
+ amount: transferAmount,
464
+ },
465
+ ];
466
+
467
+ const txParams = {
468
+ inputs,
469
+ outputs,
470
+ address: senderAddress, // charge
471
+ feePerB: 100000,
472
+ };
473
+
474
+ return { params: txParams, usingUtxos };
475
+ }
476
+
477
+ export function createDrc20Data(utxos: any[], senderAddress: string, inscription: string) {
478
+ if (!utxos || utxos.length === 0) {
479
+ throw new Error("UTXOs are required");
480
+ }
481
+ if (!senderAddress) {
482
+ throw new Error("Sender address is required");
483
+ }
484
+ if (!inscription) {
485
+ throw new Error("Inscription data is required");
486
+ }
487
+
488
+ const commitTxPrevOutputList = [];
489
+ const usedUtxos = getUsedUtxos();
490
+ const usingUtxos: any = {};
491
+
492
+ const txFeeDefault = 30000000; //0.3 DOGE
493
+ let addedAmount = 0;
494
+ utxos = utxos.sort((a, b) => b.outputValue - a.outputValue);
495
+ for (const utxo of utxos) {
496
+ const { txid, vout, outputValue, address = senderAddress } = utxo;
497
+ if (addedAmount < txFeeDefault && !usedUtxos[txid]) {
498
+ commitTxPrevOutputList.push({
499
+ txId: txid,
500
+ vOut: vout,
501
+ amount: Number(outputValue),
502
+ address,
503
+ });
504
+ usingUtxos[txid] = 1;
505
+ addedAmount += Number(outputValue);
506
+ }
507
+ }
508
+
509
+ if (addedAmount < txFeeDefault) {
510
+ throw new Error("insufficient balance.");
511
+ }
512
+
513
+ // const inscriptionHex = base.toHex(Buffer.from(inscription));
514
+ const inscriptionData = {
515
+ contentType: "text/plain;charset=utf8",
516
+ body: inscription,
517
+ revealAddr: senderAddress,
518
+ };
519
+
520
+ const request = {
521
+ type: 1,
522
+ commitTxPrevOutputList,
523
+ commitFeeRate: 10000,
524
+ revealFeeRate: 9000,
525
+ revealOutValue: 0,
526
+ inscriptionData,
527
+ changeAddress: senderAddress,
528
+ };
529
+ return { params: request, usingUtxos };
530
+ }
531
+
532
+ export function decodePsbt(psbt: string, type = "hex") {
533
+ try {
534
+ if (type === "base64") {
535
+ // Convert from base64 to hex
536
+ const psbtHex = base.toHex(base.fromBase64(psbt));
537
+ return Psbt.fromHex(psbtHex, {
538
+ network,
539
+ });
540
+ }
541
+
542
+ return Psbt.fromHex(psbt, {
543
+ network,
544
+ });
545
+ } catch (error) {
546
+ console.error("Failed to decode PSBT:", error);
547
+ throw error;
548
+ }
549
+ }
550
+
551
+ export class TransactionParser {
552
+ public rawTx: string;
553
+
554
+ constructor(rawTx: string) {
555
+ this.rawTx = rawTx;
556
+ }
557
+
558
+ hexToText(hex: string) {
559
+ let str = "";
560
+ for (let i = 0; i < hex.length; i += 2) {
561
+ str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
562
+ }
563
+ return str;
564
+ }
565
+
566
+ extractOPReturnData() {
567
+ const ordIndex = this.rawTx.indexOf("6f7264"); // 'ord' in hex
568
+ if (ordIndex === -1) return null;
569
+
570
+ const dataHex = this.rawTx.substring(ordIndex);
571
+ const dataText = this.hexToText(dataHex);
572
+
573
+ // Extract JSON data
574
+ const jsonMatch = dataText.match(/\{.*?\}/);
575
+ return jsonMatch ? JSON.parse(jsonMatch[0]) : null;
576
+ }
577
+
578
+ parseScript() {
579
+ return {
580
+ version: this.rawTx.substring(0, 8),
581
+ inputCount: parseInt(this.rawTx.substring(8, 10)),
582
+ inputs: this.parseInputs(),
583
+ opReturnData: this.extractOPReturnData(),
584
+ };
585
+ }
586
+
587
+ parseInputs() {
588
+ const inputs = [];
589
+ const position = 10; // After version and input count
590
+
591
+ // Parse first input
592
+ const firstInput = {
593
+ txid: this.rawTx.substring(position, position + 64),
594
+ vout: this.rawTx.substring(position + 64, position + 72),
595
+ scriptData: this.extractOPReturnData(),
596
+ };
597
+ inputs.push(firstInput);
598
+
599
+ return inputs;
600
+ }
601
+ }
package/src/evm/rpc.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { createPublicClient, http, parseUnits } from "viem";
2
+ import { getContract, erc20Abi, encodeFunctionData, decodeFunctionData } from "viem";
3
+
4
+ export function getRPCClient(network: any) {
5
+ const { chainId, name, rpcUrls, nativeCurrencyDecimals, nativeCurrencyName, nativeCurrencySymbol } = network;
6
+
7
+ const myCustomChain = {
8
+ id: Number(chainId) || chainId,
9
+ name,
10
+ nativeCurrency: {
11
+ name: nativeCurrencyName,
12
+ symbol: nativeCurrencySymbol,
13
+ decimals: nativeCurrencyDecimals,
14
+ },
15
+ rpcUrls: {
16
+ default: {
17
+ http: rpcUrls,
18
+ webSocket: [],
19
+ },
20
+ public: {
21
+ http: rpcUrls,
22
+ webSocket: [],
23
+ },
24
+ },
25
+ blockExplorers: {
26
+ default: {
27
+ name: "Explorer",
28
+ url: rpcUrls[0],
29
+ },
30
+ },
31
+ };
32
+
33
+ const rpcClient = createPublicClient({
34
+ chain: myCustomChain,
35
+ pollingInterval: 10_000,
36
+ cacheTime: 10_000,
37
+ transport: http(),
38
+ });
39
+
40
+ return { rpcClient };
41
+ }
42
+
43
+ export function getERC20Contract(client: any, address: `0x${string}`) {
44
+ return getContract({
45
+ address,
46
+ abi: erc20Abi,
47
+ client,
48
+ });
49
+ }
50
+
51
+ export function decodeErc20Func(data: `0x${string}`) {
52
+ return decodeFunctionData({ data, abi: erc20Abi });
53
+ }
54
+
55
+ export function createErc20TxData(params: any, token: any) {
56
+ const { decimals, address: tokenAddress } = token;
57
+ const value = parseUnits(params?.amount.toString(), decimals);
58
+ const callData = encodeFunctionData({
59
+ abi: erc20Abi,
60
+ functionName: "transfer",
61
+ args: [params.to, value],
62
+ });
63
+
64
+ return {
65
+ ...params,
66
+ ...{ amount: 0, data: callData, to: tokenAddress },
67
+ };
68
+ }