@tomo-inc/chains-service 0.0.6 → 0.0.8
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/CHANGELOG.md +4 -0
- package/dist/index.cjs +602 -95
- package/dist/index.d.cts +117 -43
- package/dist/index.d.ts +117 -43
- package/dist/index.js +608 -102
- package/package.json +2 -2
- package/src/api/network-data.ts +19 -5
- package/src/api/token.ts +11 -6
- package/src/base/service.ts +4 -4
- package/src/base/token.ts +8 -9
- package/src/dogecoin/service.ts +46 -252
- package/src/dogecoin/type.ts +12 -0
- package/src/dogecoin/utils-doge.ts +75 -2
- package/src/dogecoin/utils.ts +3 -380
- package/src/evm/service.ts +57 -73
- package/src/index.ts +5 -4
- package/src/solana/service.ts +6 -6
- package/src/types/account.ts +4 -3
- package/src/types/index.ts +3 -3
- package/src/utils/index.ts +7 -0
package/src/dogecoin/utils.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { DogecoinUtils } from "./utils-doge";
|
|
|
4
4
|
|
|
5
5
|
import * as API from "./rpc";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { DECIMALS, RPC_URL, network } from "./config";
|
|
7
|
+
import { DECIMALS, network } from "./config";
|
|
9
8
|
import { DogeSpendableUtxos } from "./type";
|
|
10
9
|
|
|
11
10
|
import * as base from "./base";
|
|
@@ -14,14 +13,6 @@ import { Psbt } from "bitcoinjs-lib";
|
|
|
14
13
|
|
|
15
14
|
const KOINU_PER_DOGE = new BigNumber(DECIMALS); // 10^8
|
|
16
15
|
|
|
17
|
-
export async function waitOnly(ms: number) {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
setTimeout(() => {
|
|
20
|
-
resolve(ms);
|
|
21
|
-
}, ms);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
16
|
export function toSatoshi(bitcion: number | string): number {
|
|
26
17
|
try {
|
|
27
18
|
const amount = new BigNumber(bitcion);
|
|
@@ -46,103 +37,6 @@ export function toBitcoin(satoshis: number | string): number {
|
|
|
46
37
|
}
|
|
47
38
|
}
|
|
48
39
|
|
|
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
40
|
export function addUsedUtxos(newUsedUtxos: object) {
|
|
147
41
|
const usedUtxos = cache.get("UsedUtxos") || {};
|
|
148
42
|
for (const txid in newUsedUtxos) {
|
|
@@ -155,107 +49,6 @@ export function getUsedUtxos() {
|
|
|
155
49
|
return cache.get("UsedUtxos") || {};
|
|
156
50
|
}
|
|
157
51
|
|
|
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
52
|
export async function createPsbt({
|
|
260
53
|
from,
|
|
261
54
|
to,
|
|
@@ -269,9 +62,9 @@ export async function createPsbt({
|
|
|
269
62
|
fee: number;
|
|
270
63
|
spendableUtxos: DogeSpendableUtxos[];
|
|
271
64
|
}) {
|
|
272
|
-
|
|
65
|
+
const utxos = spendableUtxos;
|
|
273
66
|
if (!utxos || utxos.length === 0) {
|
|
274
|
-
|
|
67
|
+
throw new Error("no spendable utxos");
|
|
275
68
|
}
|
|
276
69
|
|
|
277
70
|
const sendAmount = toSatoshi(amount);
|
|
@@ -359,176 +152,6 @@ export async function createPsbt({
|
|
|
359
152
|
return { psbtBase64, usingUtxos };
|
|
360
153
|
}
|
|
361
154
|
|
|
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
155
|
export function decodePsbt(psbt: string, type = "hex") {
|
|
533
156
|
try {
|
|
534
157
|
if (type === "base64") {
|
package/src/evm/service.ts
CHANGED
|
@@ -4,12 +4,15 @@ import {
|
|
|
4
4
|
createPublicClient,
|
|
5
5
|
formatUnits,
|
|
6
6
|
fromHex,
|
|
7
|
+
hexToBigInt,
|
|
7
8
|
http,
|
|
8
9
|
isHex,
|
|
10
|
+
numberToHex,
|
|
9
11
|
parseTransaction,
|
|
10
12
|
parseUnits,
|
|
11
13
|
toHex,
|
|
12
14
|
Transaction,
|
|
15
|
+
isAddressEqual,
|
|
13
16
|
} from "viem";
|
|
14
17
|
|
|
15
18
|
import { BaseService } from "../base/service";
|
|
@@ -20,7 +23,7 @@ import {
|
|
|
20
23
|
ITypedData,
|
|
21
24
|
INetwork,
|
|
22
25
|
TomoAppInfo,
|
|
23
|
-
|
|
26
|
+
IAccountInfo,
|
|
24
27
|
TransactionParams,
|
|
25
28
|
QueryGasResponse,
|
|
26
29
|
} from "../types";
|
|
@@ -30,12 +33,12 @@ export class EvmService extends BaseService {
|
|
|
30
33
|
private static instance: EvmService;
|
|
31
34
|
public chainType: ChainTypes | "";
|
|
32
35
|
|
|
33
|
-
public constructor(chainType: ChainTypes, accountInfo:
|
|
36
|
+
public constructor(chainType: ChainTypes, accountInfo: IAccountInfo, tomoAppInfo: TomoAppInfo) {
|
|
34
37
|
super(tomoAppInfo, accountInfo);
|
|
35
38
|
this.chainType = chainType;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
public static getInstance(chainType: ChainTypes, accountInfo:
|
|
41
|
+
public static getInstance(chainType: ChainTypes, accountInfo: IAccountInfo, tomoAppInfo: TomoAppInfo) {
|
|
39
42
|
if (!EvmService.instance) {
|
|
40
43
|
EvmService.instance = new EvmService(chainType, accountInfo, tomoAppInfo);
|
|
41
44
|
}
|
|
@@ -96,26 +99,26 @@ export class EvmService extends BaseService {
|
|
|
96
99
|
|
|
97
100
|
public async personal_sign([message, address]: [`0x${string}`, `0x${string}`]): Promise<`0x${string}`> {
|
|
98
101
|
const accounts = await this.accountInfo.getCurrent();
|
|
99
|
-
if (accounts[0].address
|
|
102
|
+
if (!isAddressEqual(accounts[0].address, address)) {
|
|
100
103
|
throw new Error("address is not the current account");
|
|
101
104
|
}
|
|
102
105
|
const signature = await this.accountInfo.signMessage(message);
|
|
103
|
-
return signature
|
|
106
|
+
return signature as `0x${string}`;
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
public async eth_signTypedData_v4([address, typedData]: [`0x${string}`, ITypedData]): Promise<`0x${string}`> {
|
|
107
110
|
const accounts = await this.accountInfo.getCurrent();
|
|
108
|
-
if (accounts[0].address
|
|
111
|
+
if (!isAddressEqual(accounts[0].address, address)) {
|
|
109
112
|
throw new Error("address is not the current account");
|
|
110
113
|
}
|
|
111
114
|
const signature = await this.accountInfo.signTypedData(typedData);
|
|
112
115
|
|
|
113
|
-
return signature
|
|
116
|
+
return signature as `0x${string}`;
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
public async eth_getBalance([address, type]: [`0x${string}`, "latest"]): Promise<string> {
|
|
117
120
|
const accounts = await this.accountInfo.getCurrent();
|
|
118
|
-
if (accounts[0].address
|
|
121
|
+
if (!isAddressEqual(accounts[0].address, address)) {
|
|
119
122
|
throw new Error("address is not the current account");
|
|
120
123
|
}
|
|
121
124
|
if (type !== "latest") {
|
|
@@ -200,58 +203,6 @@ export class EvmService extends BaseService {
|
|
|
200
203
|
};
|
|
201
204
|
}
|
|
202
205
|
|
|
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
206
|
private async createPublicClient({ chainId, rpcUrl }: { chainId?: string; rpcUrl?: string }): Promise<any> {
|
|
256
207
|
if (rpcUrl) {
|
|
257
208
|
return createPublicClient({
|
|
@@ -274,6 +225,30 @@ export class EvmService extends BaseService {
|
|
|
274
225
|
});
|
|
275
226
|
}
|
|
276
227
|
|
|
228
|
+
public async eth_estimateGas(txs: Transaction[]): Promise<`0x${string}`> {
|
|
229
|
+
const { from, to, value = "0x1", chainId } = txs[0] || {};
|
|
230
|
+
const accounts = await this.accountInfo.getCurrent();
|
|
231
|
+
if (accounts[0].address !== from) {
|
|
232
|
+
throw new Error("address is not the current account");
|
|
233
|
+
}
|
|
234
|
+
if (!to) {
|
|
235
|
+
throw new Error("to is not set");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const rpcClient = await this.createPublicClient({ chainId: chainId?.toString() });
|
|
240
|
+
const gas = await rpcClient.estimateGas({ account: from, to, value: hexToBigInt(value as `0x${string}`) });
|
|
241
|
+
return gas;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.warn("Failed to estimate gas:", error);
|
|
244
|
+
// throw new Error(`Failed to estimate gas: ${error}`);
|
|
245
|
+
|
|
246
|
+
const BaseConfig = SupportedChainTypes[ChainTypes.EVM];
|
|
247
|
+
const { gasFee } = BaseConfig;
|
|
248
|
+
return numberToHex(parseUnits(gasFee.toString(), 18));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
277
252
|
// Get nonce for current account
|
|
278
253
|
public async eth_getTransactionCount([address, type]: [`0x${string}`, "latest" | "pending"]): Promise<number> {
|
|
279
254
|
const accounts = await this.accountInfo.getCurrent();
|
|
@@ -297,14 +272,16 @@ export class EvmService extends BaseService {
|
|
|
297
272
|
//https://docs.metamask.io/snaps/reference/keyring-api/chain-methods/#eth_signtransaction
|
|
298
273
|
public async eth_signTransaction(
|
|
299
274
|
txParams: {
|
|
300
|
-
type?:
|
|
275
|
+
type?: "eip1559" | "legacy";
|
|
301
276
|
from: `0x${string}`;
|
|
302
277
|
to: `0x${string}`;
|
|
303
278
|
value: `0x${string}`;
|
|
304
279
|
data?: `0x${string}`;
|
|
305
280
|
chainId: `0x${string}`;
|
|
306
281
|
nonce: `0x${string}`;
|
|
307
|
-
gasLimit
|
|
282
|
+
gasLimit?: `0x${string}`;
|
|
283
|
+
gas?: `0x${string}`;
|
|
284
|
+
gasPrice?: `0x${string}`;
|
|
308
285
|
maxFeePerGas?: `0x${string}`;
|
|
309
286
|
maxPriorityFeePerGas?: `0x${string}`;
|
|
310
287
|
accessList?: any[];
|
|
@@ -318,32 +295,39 @@ export class EvmService extends BaseService {
|
|
|
318
295
|
nonce: "number",
|
|
319
296
|
maxPriorityFeePerGas: "bigint",
|
|
320
297
|
maxFeePerGas: "bigint",
|
|
321
|
-
|
|
298
|
+
gasPrice: "bigint",
|
|
322
299
|
gas: "bigint",
|
|
323
300
|
};
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
txData = { ...txData, gas, gasLimit: gas };
|
|
301
|
+
const txData: any = txParams?.[0];
|
|
302
|
+
txData.gas = txData?.gas || txData?.gasLimit;
|
|
327
303
|
txData.data = txData.data || "0x";
|
|
304
|
+
txData.value = txData.value || "0x";
|
|
305
|
+
txData.nonce = txData.nonce || "0x0";
|
|
306
|
+
|
|
307
|
+
if (!txData?.gas || !txData?.to || !txData?.chainId) {
|
|
308
|
+
throw new Error("gas or to or chainId is not set");
|
|
309
|
+
}
|
|
328
310
|
|
|
329
311
|
const accounts = await this.accountInfo.getCurrent();
|
|
330
312
|
const address = accounts[0].address;
|
|
331
|
-
if (txData?.from && txData?.from
|
|
313
|
+
if (txData?.from && !isAddressEqual(txData?.from, address)) {
|
|
332
314
|
console.error("eth_signTransaction error data: from is not the current account", txData);
|
|
333
315
|
throw new Error(`eth_signTransaction error data: from is not the current account`);
|
|
334
316
|
}
|
|
335
317
|
delete txData?.from;
|
|
318
|
+
delete txData?.gasLimit;
|
|
336
319
|
|
|
337
320
|
const preparedTx: any = {
|
|
338
|
-
|
|
339
|
-
gas: txData?.gasLimit,
|
|
340
|
-
account: address,
|
|
321
|
+
gas: txData?.gas,
|
|
341
322
|
};
|
|
342
323
|
for (const key in preparedTxPropsType) {
|
|
343
|
-
if (
|
|
344
|
-
|
|
324
|
+
if (txData?.[key] && isHex(txData?.[key])) {
|
|
325
|
+
preparedTx[key] = preparedTxPropsType[key] === "number" ? fromHex(txData[key], "number") : txData[key];
|
|
345
326
|
}
|
|
346
|
-
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (txData?.type !== "legacy") {
|
|
330
|
+
delete preparedTx.gasPrice;
|
|
347
331
|
}
|
|
348
332
|
|
|
349
333
|
try {
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { EvmService } from "./evm/service";
|
|
2
2
|
import { SolanaService } from "./solana/service";
|
|
3
|
-
|
|
3
|
+
import { DogecoinService } from "./dogecoin/service";
|
|
4
4
|
import { ChainTypes } from "@tomo-inc/wallet-utils";
|
|
5
5
|
|
|
6
|
-
export { EvmService, SolanaService };
|
|
6
|
+
export { EvmService, SolanaService, DogecoinService };
|
|
7
7
|
|
|
8
8
|
export const ChainTypeServices = {
|
|
9
9
|
[ChainTypes.EVM]: EvmService,
|
|
10
10
|
[ChainTypes.SOL]: SolanaService,
|
|
11
|
-
|
|
11
|
+
[ChainTypes.DOGE]: DogecoinService,
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export { TomoWallet } from "./wallet";
|
|
@@ -18,11 +18,12 @@ export { TomoWallet } from "./wallet";
|
|
|
18
18
|
|
|
19
19
|
export type {
|
|
20
20
|
ChainAddress,
|
|
21
|
+
IAccountInfo,
|
|
21
22
|
IAccount,
|
|
22
23
|
TomoAppInfo,
|
|
23
24
|
TransactionItem,
|
|
24
25
|
TransactionsParams,
|
|
25
|
-
TransactionsResponse
|
|
26
|
+
TransactionsResponse,
|
|
26
27
|
} from "./types";
|
|
27
28
|
|
|
28
29
|
export { AccountType, TxTypes } from "./types";
|