@tomo-inc/chains-service 0.0.7 → 0.0.9

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.
@@ -6,37 +6,44 @@ import { BigNumber } from "bignumber.js";
6
6
  import { DogecoinUtils } from "./utils-doge";
7
7
  import { parseUnits, toHex } from "viem";
8
8
 
9
- import { IaccountInfo, QueryGasParams, QueryGasResponse, TomoAppInfo } from "../types";
10
- import { TransactionParams, DunesTransactionParams } from "./type";
9
+ import { IAccountInfo, QueryGasParams, QueryGasResponse, TomoAppInfo } from "../types";
10
+ import { TransactionParams } from "./type";
11
11
 
12
12
  import * as API from "./rpc";
13
13
  import * as utils from "./utils";
14
- import { DogeTxData, waitOnly } from "./utils";
14
+ import { DogeTxData } from "./type";
15
15
 
16
16
  import { BaseConfig } from "./config";
17
+ import { createPsbt, addUsedUtxos } from "./utils";
17
18
 
18
19
  export class DogecoinService extends BaseService {
19
20
  private static instance: DogecoinService;
20
21
  public chainType: ChainTypes | "";
22
+ public rpcService: typeof API;
21
23
 
22
- public constructor(chainType: ChainTypes, accountInfo: IaccountInfo, tomoAppInfo: TomoAppInfo) {
24
+ public constructor(chainType: ChainTypes, accountInfo: IAccountInfo, tomoAppInfo: TomoAppInfo) {
23
25
  super(tomoAppInfo, accountInfo);
24
26
  this.chainType = chainType;
27
+ this.rpcService = API;
25
28
  }
26
29
 
27
30
  //singleton
28
- public static getInstance(chainType: ChainTypes, accountInfo: any, tomoAppInfo: TomoAppInfo) {
31
+ public static getInstance(chainType: ChainTypes, accountInfo: IAccountInfo, tomoAppInfo: TomoAppInfo) {
29
32
  if (!DogecoinService.instance) {
30
33
  DogecoinService.instance = new DogecoinService(chainType, accountInfo, tomoAppInfo);
31
34
  }
32
35
  return DogecoinService.instance;
33
36
  }
34
37
  public async requestAccounts(): Promise<{ address: string; balance: number; approved: boolean; publicKey: string }> {
35
- const { currentAddress, publicKey = "" }: any = await this.accountInfo.getIds();
36
- const { balance = 0 } = await API.getBalance(currentAddress);
38
+ const addresses = await this.accountInfo.getCurrent();
39
+ const { publicKey, address } = addresses?.[0] || {};
40
+ if (!address) {
41
+ throw new Error("address is not set");
42
+ }
43
+ const { balance = 0 } = await API.getBalance(address);
37
44
 
38
45
  return {
39
- address: currentAddress,
46
+ address,
40
47
  balance,
41
48
  approved: true,
42
49
  publicKey,
@@ -44,21 +51,29 @@ export class DogecoinService extends BaseService {
44
51
  }
45
52
 
46
53
  public async getAccounts(): Promise<string[]> {
47
- const { addresses = [] }: any = await this.accountInfo.getIds();
48
- return addresses;
54
+ const accounts = await this.accountInfo.getCurrent();
55
+ return accounts.map((account: { address: string }) => account.address);
49
56
  }
50
57
 
51
58
  public async getConnectionStatus(): Promise<{ connected: boolean; address: string; selectedWalletAddress: string }> {
52
- const { currentAddress }: any = await this.accountInfo.getIds();
59
+ const addresses = await this.accountInfo.getCurrent();
60
+ const { address } = addresses?.[0] || {};
61
+ if (!address) {
62
+ throw new Error("address is not set");
63
+ }
53
64
  return {
54
65
  connected: true,
55
- address: currentAddress,
56
- selectedWalletAddress: currentAddress,
66
+ address,
67
+ selectedWalletAddress: address,
57
68
  };
58
69
  }
59
70
 
60
71
  public async getBalance(): Promise<{ address: string; balance: number }> {
61
- const { currentAddress: address }: any = await this.accountInfo.getIds();
72
+ const addresses = await this.accountInfo.getCurrent();
73
+ const { address } = addresses?.[0] || {};
74
+ if (!address) {
75
+ throw new Error("address is not set");
76
+ }
62
77
 
63
78
  const { balance = 0 } = await API.getBalance(address);
64
79
 
@@ -68,12 +83,12 @@ export class DogecoinService extends BaseService {
68
83
  };
69
84
  }
70
85
 
71
- public async signMessage({ text, type }: { text: string; type?: string }): Promise<{ signedMessage: string }> {
72
- if (type !== "") {
86
+ public async signMessage({ message, type }: { message: string; type?: string }): Promise<{ signedMessage: string }> {
87
+ if (type) {
73
88
  throw new Error("bip322simple not support.");
74
89
  }
75
90
 
76
- const signature = await this.accountInfo.signMessage(text);
91
+ const signature = await this.accountInfo.signMessage(message);
77
92
 
78
93
  return {
79
94
  signedMessage: signature,
@@ -81,31 +96,18 @@ export class DogecoinService extends BaseService {
81
96
  }
82
97
 
83
98
  public async requestSignedMessage({
84
- text,
99
+ message,
85
100
  type,
86
101
  }: {
87
- text: string;
102
+ message: string;
88
103
  type?: string;
89
104
  }): Promise<{ signedMessage: string }> {
90
105
  return await this.signMessage({
91
- text,
106
+ message,
92
107
  type,
93
108
  });
94
109
  }
95
110
 
96
- public async requestDecryptedMessage({ message }: { message: string }): Promise<{ decryptedMessage: string }> {
97
- try {
98
- const unsignRes = await this.accountInfo.decryptedMessage(message);
99
- const { text = "" } = JSON.parse(unsignRes);
100
-
101
- return {
102
- decryptedMessage: text,
103
- };
104
- } catch (err) {
105
- throw new Error("requestDecryptedMessage err:" + JSON.stringify(err));
106
- }
107
- }
108
-
109
111
  public async _queryGasInfo(txData: TransactionParams): Promise<QueryGasResponse> {
110
112
  const { chainId, amount = 0, decimals = 8 } = txData;
111
113
  const gasLimitParam: any = {
@@ -114,7 +116,7 @@ export class DogecoinService extends BaseService {
114
116
  value: toHex(parseUnits(amount.toString(), decimals)),
115
117
  };
116
118
  const queryGasParams: QueryGasParams = {
117
- chainIndex: Number(chainId),
119
+ chainIndex: Number(chainId || BaseConfig.chainId),
118
120
  callData: "0x",
119
121
  gasLimitParam,
120
122
  addressList: [txData.from],
@@ -164,19 +166,14 @@ export class DogecoinService extends BaseService {
164
166
  rawTx: string;
165
167
  signOnly?: boolean;
166
168
  }): Promise<{ signedRawTx: string; txId?: string } | null> {
167
- const params = {
168
- type: "signRawTx",
169
- data: {
170
- walletId: -1,
171
- rawTx,
172
- },
173
- };
174
- const signedRawTx = await this.accountInfo.signTransaction(JSON.stringify(params));
169
+ const psbtBase64 = await DogecoinUtils.rawTxToPsbtBase64(rawTx);
170
+ const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify({ psbtBase64 }));
175
171
 
176
- if (!signedRawTx) {
172
+ if (!signedPsbt) {
177
173
  return null;
178
174
  }
179
175
 
176
+ const signedRawTx = DogecoinUtils.extractPsbtTransaction(signedPsbt, 100000);
180
177
  if (signOnly) {
181
178
  return {
182
179
  signedRawTx,
@@ -200,18 +197,16 @@ export class DogecoinService extends BaseService {
200
197
  txData.spendableUtxos = await API.getSpendableUtxos(txData.from);
201
198
  }
202
199
 
203
- let signedTx = "";
204
- const _UsingUtxos = {};
205
- const dogeTxParams = await utils.createPsbt(txData as any);
206
- const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify(dogeTxParams));
207
- signedTx = DogecoinUtils.extractPsbtTransaction(signedPsbt, 100000);
200
+ const { psbtBase64, usingUtxos } = await createPsbt(txData as any);
201
+ const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify({ psbtBase64 }));
202
+ const signedTx = DogecoinUtils.extractPsbtTransaction(signedPsbt, 100000);
208
203
 
209
204
  if (signedTx === "") {
210
205
  throw new Error("Error: sign transaction err.");
211
206
  }
212
207
  try {
213
208
  const { txid } = await API.sendDogeTx(signedTx);
214
- utils.addUsedUtxos(_UsingUtxos);
209
+ addUsedUtxos(usingUtxos);
215
210
  return { txId: txid };
216
211
  } catch (err) {
217
212
  throw new Error("Error: send transaction err." + JSON.stringify(err));
@@ -233,219 +228,4 @@ export class DogecoinService extends BaseService {
233
228
  address: transaction.vout[0]?.addresses[0],
234
229
  };
235
230
  }
236
-
237
- public async getDRC20Balance(params: {
238
- ticker: string;
239
- }): Promise<{ availableBalance: number; transferableBalance: number; ticker: string; address: string }> {
240
- const { currentAddress: address }: any = await this.accountInfo.getIds();
241
- const { ticker } = params;
242
-
243
- const { balances = [] } = await API.getDRC20Balance(address, ticker);
244
-
245
- let availableBalance = 0;
246
- let transferableBalance = 0;
247
- if (balances.length) {
248
- availableBalance = balances[0].availableBalance;
249
- transferableBalance = balances[0].transferableBalance;
250
- }
251
-
252
- return {
253
- availableBalance,
254
- transferableBalance,
255
- ticker,
256
- address,
257
- };
258
- }
259
-
260
- public async getTransferableDRC20(params: {
261
- ticker: string;
262
- }): Promise<{ inscriptions: any[]; ticker: string; address: string }> {
263
- const { currentAddress: address }: any = await this.accountInfo.getIds();
264
- const { ticker } = params;
265
-
266
- const query = await API.getDRC20Inscriptions(address, ticker);
267
-
268
- return {
269
- inscriptions: query.list || [],
270
- ticker: ticker,
271
- address: address,
272
- };
273
- }
274
-
275
- public async requestAvailableDRC20Transaction({
276
- ticker,
277
- amount,
278
- }: {
279
- ticker: string;
280
- amount: number;
281
- }): Promise<{ txId: string; ticker: string; amount: number }> {
282
- const inscription = `{"p":"drc-20","op":"transfer","tick":"${ticker}","amt":"${amount}"}`;
283
- let params = null;
284
- let usingUtxos = null;
285
-
286
- try {
287
- const { currentAddress: senderAddress }: any = await this.accountInfo.getIds();
288
- const utxos = await API.getSpendableUtxos(senderAddress);
289
- const res = utils.createDrc20Data(utxos, senderAddress, inscription);
290
- params = res.params;
291
- usingUtxos = res.usingUtxos || {};
292
- } catch (err) {
293
- throw new Error("Error: sign transaction err." + JSON.stringify(err));
294
- }
295
-
296
- const signedTxs = await this.accountInfo.signTransaction(params);
297
- const { commitTx = "", revealTxs = [] } = signedTxs;
298
- const revealTx = revealTxs[0] || "";
299
- if (commitTx === "" || revealTx === "") {
300
- throw new Error("Error: sign transaction err.");
301
- }
302
- const { txid: commitTxId } = await API.sendDogeTx(commitTx);
303
- await waitOnly(0.3 * 1000); //ms
304
- const { txid: revealTxId } = await API.sendDogeTx(revealTx);
305
- if (revealTxId && commitTxId) {
306
- utils.addUsedUtxos(usingUtxos);
307
- }
308
-
309
- return {
310
- txId: revealTxId,
311
- ticker,
312
- amount,
313
- };
314
- }
315
-
316
- public async getInscriptionTransactionDetail(params: { recipientAddress: string; location: string }): Promise<{
317
- senderAddress: string;
318
- inscription: any;
319
- inputTxid: string;
320
- fee: number;
321
- amount: number;
322
- rawTx: string;
323
- } | null> {
324
- const { currentAddress: senderAddress }: any = await this.accountInfo.getIds();
325
- const { recipientAddress, location } = params;
326
-
327
- const locations: string[] = location.split(":");
328
- const inputTxid: string = locations[0] || "";
329
- const vout = Number(locations[1] || 0);
330
- const offset = Number(locations[2] || 0);
331
-
332
- if (inputTxid?.length !== 64 || Number.isNaN(vout)) {
333
- return null;
334
- }
335
-
336
- const res = await API.getInscriptionsUtxo(senderAddress, {
337
- txid: inputTxid,
338
- vout,
339
- });
340
- const { inscriptions = [] } = res || {};
341
-
342
- if (inscriptions.length === 0) {
343
- return null;
344
- }
345
-
346
- const inscription = inscriptions?.find((i: any) => i.offset === offset);
347
- const { rawTx, fee, amount }: any = await API.createNFTTransaction({
348
- senderAddress,
349
- recipientAddress,
350
- location,
351
- inscriptionId: inscription.inscription_id,
352
- });
353
-
354
- return {
355
- senderAddress,
356
- inscription,
357
- inputTxid,
358
- fee,
359
- amount,
360
- rawTx,
361
- };
362
- }
363
-
364
- public async requestInscriptionTransaction(params: {
365
- recipientAddress: string;
366
- location: string;
367
- }): Promise<{ txId: string }> {
368
- const { currentAddress: senderAddress }: any = await this.accountInfo.getIds();
369
- const { recipientAddress, location } = params;
370
-
371
- const locations: string[] = location.split(":");
372
- const inputTxid: string = locations[0] || "";
373
-
374
- const detail = await this.getInscriptionTransactionDetail(params);
375
- if (!detail) {
376
- throw new Error("Error: location not found");
377
- }
378
-
379
- const { fee, amount }: any = detail || {};
380
-
381
- const utxos = await API.getSpendableUtxos(senderAddress);
382
- const { psbtBase64, usingUtxos } = await utils.createInscriptionPsbt({
383
- inscriptionId: inputTxid,
384
- senderAddress,
385
- recipientAddress,
386
- utxos,
387
- fee,
388
- amount,
389
- });
390
-
391
- const signedRawTx = await this.accountInfo.signTransaction({
392
- type: 2,
393
- psbt: psbtBase64,
394
- });
395
- const tx = DogecoinUtils.extractPsbtTransaction(signedRawTx, 100000);
396
-
397
- const { txid } = await API.sendDogeTx(tx);
398
-
399
- if (txid) {
400
- utils.addUsedUtxos(usingUtxos);
401
- }
402
-
403
- return {
404
- txId: txid,
405
- };
406
- }
407
-
408
- public async getDunesBalance(params?: {
409
- ticker: string;
410
- }): Promise<{ balance: number; ticker: string; address: string }> {
411
- const { ticker = "" } = params || {};
412
-
413
- if (!ticker) {
414
- throw new Error("Error: ticker is required");
415
- }
416
-
417
- const { currentAddress: address }: any = await this.accountInfo.getIds();
418
- const { balances = [] } = await API.getDunesBalance(address, ticker);
419
-
420
- let balance = 0;
421
- if (balances.length) {
422
- balance = balances[0].overallBalance;
423
- }
424
- return {
425
- balance,
426
- ticker,
427
- address,
428
- };
429
- }
430
-
431
- public async requestDunesTransaction(params: DunesTransactionParams): Promise<{ txId: string }> {
432
- const { currentAddress: senderAddress }: any = await this.accountInfo.getIds();
433
- const { rawTx: rawTransaction, inputs }: any = await API.createDunesTransaction({ ...params, senderAddress });
434
-
435
- //utxo
436
-
437
- const signParams = {
438
- rawTransaction,
439
- };
440
- const signedRawTx = await this.accountInfo.signTransaction(signParams);
441
-
442
- if (!signedRawTx) {
443
- throw new Error("Error: sign transaction err.");
444
- }
445
-
446
- const { txid = "" } = await API.sendDogeTx(signedRawTx);
447
- return {
448
- txId: txid,
449
- };
450
- }
451
231
  }
@@ -27,3 +27,15 @@ export interface TransactionParams {
27
27
  chainId?: number | string;
28
28
  tokenAddress?: string;
29
29
  }
30
+
31
+ export interface DogeTxData {
32
+ amountMismatch?: boolean;
33
+ amount: number;
34
+ fee: number;
35
+ to: string;
36
+ from: string;
37
+ senderAddress?: string;
38
+ sender?: string;
39
+ fromAddress?: string;
40
+ spendableUtxos?: DogeSpendableUtxos[];
41
+ }
@@ -1,6 +1,7 @@
1
- import { address as AddressLib, Psbt } from "bitcoinjs-lib";
1
+ import { address as AddressLib, Psbt, Transaction } from "bitcoinjs-lib";
2
2
  import * as base from "./base";
3
3
  import { network } from "./config";
4
+ import * as APIs from "./rpc";
4
5
 
5
6
  /**
6
7
  * UTXO Transaction type definition
@@ -67,8 +68,8 @@ export class DogecoinUtils {
67
68
  // Add outputs
68
69
  for (const output of tx.outputs) {
69
70
  psbt.addOutput({
71
+ value: BigInt(output?.amount || 0),
70
72
  address: output.address,
71
- value: BigInt(output.amount),
72
73
  });
73
74
  }
74
75
 
@@ -94,6 +95,9 @@ export class DogecoinUtils {
94
95
  network,
95
96
  });
96
97
 
98
+ const maximumFeeRate = _maximumFeeRate || 100000;
99
+ psbt.setMaximumFeeRate(maximumFeeRate);
100
+
97
101
  // Note: Fee rate check may be inaccurate when extracting transaction
98
102
  // For strict fee rate checking, it should be calculated manually after extracting the transaction
99
103
 
@@ -102,4 +106,86 @@ export class DogecoinUtils {
102
106
  const transaction = psbt.extractTransaction();
103
107
  return transaction.toHex();
104
108
  }
109
+
110
+ /**
111
+ * Convert raw transaction hex to PSBT base64
112
+ * This method converts an unsigned raw transaction to PSBT format.
113
+ * Note: The rawTx should be an unsigned transaction, and the nonWitnessUtxo
114
+ * for each input will need to be fetched separately from the blockchain.
115
+ * @param rawTx hex string of the raw transaction
116
+ * @returns base64 string of the PSBT
117
+ */
118
+ static async rawTxToPsbtBase64(rawTx: string): Promise<string> {
119
+ try {
120
+ // Clean the hex string (remove 0x prefix if present)
121
+ const cleanHex = rawTx.startsWith("0x") ? rawTx.slice(2) : rawTx;
122
+
123
+ // Parse the raw transaction
124
+ const transaction = Transaction.fromHex(cleanHex);
125
+
126
+ // Create a new PSBT
127
+ const psbt = new Psbt({ network });
128
+
129
+ // Add inputs from the transaction
130
+ // Each input references a previous transaction's output
131
+ for (let i = 0; i < transaction.ins.length; i++) {
132
+ const input = transaction.ins[i];
133
+ const hash = Buffer.from(input.hash).toString("hex");
134
+ const index = input.index;
135
+
136
+ // Add input without nonWitnessUtxo initially
137
+ // The nonWitnessUtxo (full previous transaction) should be added separately
138
+ // if needed for signing. For now, we create the PSBT structure.
139
+ const inputOptions: any = {
140
+ hash,
141
+ index,
142
+ };
143
+
144
+ try {
145
+ const prevTxDetail = await APIs.getTxDetail(hash);
146
+ if (prevTxDetail?.hex) {
147
+ inputOptions.nonWitnessUtxo = Buffer.from(prevTxDetail.hex, "hex");
148
+ } else {
149
+ throw new Error(`Previous transaction ${hash} has no hex data`);
150
+ }
151
+ } catch (error) {
152
+ throw new Error(
153
+ `Failed to fetch previous transaction ${hash} for input #${i}. ` +
154
+ `This is required for PSBT creation. Error: ${error instanceof Error ? error.message : String(error)}`,
155
+ );
156
+ }
157
+
158
+ // If the transaction is unsigned and we have the script, we can add it
159
+ // Otherwise, nonWitnessUtxo will need to be fetched from RPC
160
+ psbt.addInput(inputOptions);
161
+ }
162
+
163
+ // Add outputs from the transaction
164
+ for (const output of transaction.outs) {
165
+ // Extract address and value from the output script
166
+ try {
167
+ const address = AddressLib.fromOutputScript(output.script, network);
168
+ psbt.addOutput({
169
+ address,
170
+ value: BigInt(output?.value || 0),
171
+ });
172
+ } catch (error) {
173
+ // If address extraction fails (e.g., OP_RETURN), add output with script and value
174
+ psbt.addOutput({
175
+ script: output.script,
176
+ value: BigInt(output?.value || 0),
177
+ });
178
+ }
179
+ }
180
+
181
+ // Convert PSBT to base64
182
+ const psbtHex = psbt.toHex();
183
+ return base.toBase64(base.fromHex(psbtHex));
184
+ } catch (error) {
185
+ console.warn("rawTxToPsbtBase64 failed:", error);
186
+ throw new Error(
187
+ `Failed to convert raw transaction to PSBT: ${error instanceof Error ? error.message : String(error)}`,
188
+ );
189
+ }
190
+ }
105
191
  }