@tomo-inc/chains-service 0.0.8 → 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.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { ChainTypes, SupportedChainTypes, cache, TomoApiDomains, getExplorerUrl } from '@tomo-inc/wallet-utils';
2
- import Bignumber, { BigNumber } from 'bignumber.js';
3
- import { toHex, parseUnits, isAddressEqual, createPublicClient, http, hexToBigInt, numberToHex, isHex, fromHex, parseTransaction, formatUnits, encodeFunctionData, erc20Abi } from 'viem';
1
+ import { ChainTypes, SupportedChainTypes, TomoApiDomains, cache, getExplorerUrl } from '@tomo-inc/wallet-utils';
4
2
  import axios from 'axios';
5
3
  import CryptoJS from 'crypto-js';
4
+ import Bignumber, { BigNumber } from 'bignumber.js';
5
+ import { Psbt, Transaction, address } from 'bitcoinjs-lib';
6
+ import { toHex as toHex$1, parseUnits, isAddressEqual, createPublicClient, http, hexToBigInt, numberToHex, isHex, fromHex as fromHex$1, parseTransaction, formatUnits, encodeFunctionData, erc20Abi } from 'viem';
6
7
  import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, createTransferInstruction, TOKEN_PROGRAM_ID } from '@solana/spl-token';
7
8
  import { PublicKey, Connection, VersionedTransaction, sendAndConfirmRawTransaction, Transaction as Transaction$1, SystemProgram, LAMPORTS_PER_SOL } from '@solana/web3.js';
8
- import { Psbt, Transaction, address } from 'bitcoinjs-lib';
9
9
 
10
10
  var __create = Object.create;
11
11
  var __defProp = Object.defineProperty;
@@ -16,6 +16,10 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
16
  var __commonJS = (cb, mod) => function __require() {
17
17
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
18
18
  };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, { get: all[name], enumerable: true });
22
+ };
19
23
  var __copyProps = (to, from, except, desc) => {
20
24
  if (from && typeof from === "object" || typeof from === "function") {
21
25
  for (let key of __getOwnPropNames(from))
@@ -3276,6 +3280,20 @@ var loadNetworks = (WALLET_DOMAIN) => {
3276
3280
  supportGift: false,
3277
3281
  supportHistory: true
3278
3282
  },
3283
+ {
3284
+ chainId: 36888,
3285
+ chainIndex: 3688800,
3286
+ name: "ABCORE",
3287
+ chainName: "AB Core",
3288
+ rpcUrls: ["https://rpc1.core.ab.org"],
3289
+ blockExplorerUrl: "https://explorer.core.ab.org",
3290
+ platformType: "EVM",
3291
+ isTestnet: false,
3292
+ icon: "/assets/ab.svg",
3293
+ supportSwap: true,
3294
+ supportGift: false,
3295
+ supportHistory: true
3296
+ },
3279
3297
  {
3280
3298
  chainId: 6281971,
3281
3299
  chainIndex: 628197100,
@@ -3914,571 +3932,903 @@ var BaseService = class {
3914
3932
  this.approveParams = params;
3915
3933
  }
3916
3934
  };
3917
- function getRPCClient(network2) {
3918
- const { chainId, name, rpcUrls, nativeCurrencyDecimals, nativeCurrencyName, nativeCurrencySymbol } = network2;
3919
- const myCustomChain = {
3920
- id: Number(chainId) || chainId,
3921
- name,
3922
- nativeCurrency: {
3923
- name: nativeCurrencyName,
3924
- symbol: nativeCurrencySymbol,
3925
- decimals: nativeCurrencyDecimals
3926
- },
3927
- rpcUrls: {
3928
- default: {
3929
- http: rpcUrls,
3930
- webSocket: []
3931
- },
3932
- public: {
3933
- http: rpcUrls,
3934
- webSocket: []
3935
- }
3936
- },
3937
- blockExplorers: {
3938
- default: {
3939
- name: "Explorer",
3940
- url: rpcUrls[0]
3941
- }
3942
- }
3943
- };
3944
- const rpcClient = createPublicClient({
3945
- chain: myCustomChain,
3946
- pollingInterval: 1e4,
3947
- cacheTime: 1e4,
3948
- transport: http()
3949
- });
3950
- return { rpcClient };
3951
- }
3952
-
3953
- // src/types/account.ts
3954
- var AccountType = /* @__PURE__ */ ((AccountType2) => {
3955
- AccountType2["EOA"] = "EOA";
3956
- AccountType2["SOCIAL"] = "SOCIAL";
3957
- return AccountType2;
3958
- })(AccountType || {});
3959
-
3960
- // src/types/wallet.ts
3961
- var TxTypes = /* @__PURE__ */ ((TxTypes2) => {
3962
- TxTypes2[TxTypes2["swap"] = 1] = "swap";
3963
- TxTypes2[TxTypes2["bridge"] = 2] = "bridge";
3964
- TxTypes2[TxTypes2["receive"] = 31] = "receive";
3965
- TxTypes2[TxTypes2["send"] = 32] = "send";
3966
- TxTypes2[TxTypes2["approve"] = 4] = "approve";
3967
- TxTypes2[TxTypes2["contractInteraction"] = 5] = "contractInteraction";
3968
- TxTypes2[TxTypes2["redPocket"] = 6] = "redPocket";
3969
- return TxTypes2;
3970
- })(TxTypes || {});
3971
3935
 
3972
- // src/evm/utils.ts
3973
- var isEvmChain = (network2) => {
3974
- return network2 && network2?.platformType === "EVM";
3975
- };
3976
- var getAllTypeChainIds = ({ chainId, chainType }) => {
3977
- if (isHex(chainId)) {
3978
- const chainIdHex2 = chainId;
3979
- chainId = fromHex(chainId, "number").toString();
3980
- return {
3981
- chainId,
3982
- chainIdHex: chainIdHex2,
3983
- chainUid: `${chainType}:${chainId}`
3984
- };
3936
+ // src/dogecoin/base.ts
3937
+ function fromHex(hex) {
3938
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
3939
+ const paddedHex = cleanHex.length % 2 === 0 ? cleanHex : "0" + cleanHex;
3940
+ const bytes = new Uint8Array(paddedHex.length / 2);
3941
+ for (let i = 0; i < paddedHex.length; i += 2) {
3942
+ bytes[i / 2] = parseInt(paddedHex.substr(i, 2), 16);
3985
3943
  }
3986
- const chainIdHex = toHex(Number(chainId));
3987
- return {
3988
- chainId,
3989
- chainIdHex,
3990
- chainUid: `${chainType}:${chainId}`
3991
- };
3992
- };
3993
- function createErc20TxData(params, token) {
3994
- const { decimals, address: tokenAddress } = token;
3995
- const value = parseUnits(params?.amount.toString(), decimals);
3996
- const callData = encodeFunctionData({
3997
- abi: erc20Abi,
3998
- functionName: "transfer",
3999
- args: [params.to, value]
4000
- });
4001
- return {
4002
- ...params,
4003
- ...{ amount: 0, data: callData, to: tokenAddress }
4004
- };
3944
+ return bytes;
3945
+ }
3946
+ function toHex(data) {
3947
+ if (typeof data === "string") {
3948
+ return data.startsWith("0x") ? data.slice(2) : data;
3949
+ }
3950
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
3951
+ return buffer.toString("hex");
3952
+ }
3953
+ function toBase64(data) {
3954
+ if (typeof data === "string") {
3955
+ const buffer2 = Buffer.from(data, "hex");
3956
+ return buffer2.toString("base64");
3957
+ }
3958
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
3959
+ return buffer.toString("base64");
3960
+ }
3961
+ function fromBase64(base64) {
3962
+ const buffer = Buffer.from(base64, "base64");
3963
+ return new Uint8Array(buffer);
3964
+ }
3965
+ function toBase58(data) {
3966
+ throw new Error("toBase58 requires bs58 package. Install it: pnpm add bs58");
4005
3967
  }
3968
+ var BaseConfig = SupportedChainTypes[ChainTypes.DOGE];
3969
+ var BLOCK_CONFIRMATIONS = 1;
3970
+ var FEE_RATE_KB = 0.5;
3971
+ var DECIMALS = 1e8;
3972
+ var TRANSACTION_PAGE_SIZE = 10;
3973
+ var MYDOGE_BASE_URL = "https://api.mydoge.com";
3974
+ var RPC_URL = "https://api.bitcore.io/api/DOGE/mainnet";
3975
+ var RPC_TIMEOUT = 20 * 1e3;
3976
+ var TX_OVERHEAD = 10;
3977
+ var P2SH_INPUT_SIZE = 148;
3978
+ var TX_OUTPUT_SIZE = 34;
3979
+ var DEFAULT_OUTPUT_COUNT = 2;
3980
+ var TX_SIZE = 1 * P2SH_INPUT_SIZE + DEFAULT_OUTPUT_COUNT * TX_OUTPUT_SIZE + TX_OVERHEAD;
3981
+ var network = {
3982
+ messagePrefix: "Dogecoin Signed Message:\n",
3983
+ bech32: "dc",
3984
+ bip44: 3,
3985
+ bip32: {
3986
+ public: 49990397,
3987
+ private: 49988504
3988
+ },
3989
+ pubKeyHash: 30,
3990
+ scriptHash: 22,
3991
+ wif: 158
3992
+ };
4006
3993
 
4007
- // src/evm/service.ts
4008
- var EvmService = class _EvmService extends BaseService {
4009
- static instance;
4010
- chainType;
4011
- constructor(chainType, accountInfo, tomoAppInfo) {
4012
- super(tomoAppInfo, accountInfo);
4013
- this.chainType = chainType;
3994
+ // src/dogecoin/rpc.ts
3995
+ var rpc_exports = {};
3996
+ __export(rpc_exports, {
3997
+ estimateSmartFee: () => estimateSmartFee,
3998
+ getBalance: () => getBalance,
3999
+ getDogeFeeByBlock: () => getDogeFeeByBlock,
4000
+ getInscriptionsUtxo: () => getInscriptionsUtxo,
4001
+ getInscriptionsUtxos: () => getInscriptionsUtxos,
4002
+ getSpendableUtxos: () => getSpendableUtxos,
4003
+ getTransactions: () => getTransactions,
4004
+ getTxDetail: () => getTxDetail,
4005
+ getUnSpentUtxos: () => getUnSpentUtxos,
4006
+ mydoge: () => mydoge,
4007
+ onCreateTransaction: () => onCreateTransaction,
4008
+ sendDogeTx: () => sendDogeTx,
4009
+ sendTransaction: () => sendTransaction
4010
+ });
4011
+ var KOINU_PER_DOGE = new BigNumber(DECIMALS);
4012
+ function toSatoshi(bitcion) {
4013
+ try {
4014
+ const amount = new BigNumber(bitcion);
4015
+ if (amount.isNaN() || amount.isNegative()) {
4016
+ throw new Error("Invalid amount");
4017
+ }
4018
+ return amount.times(KOINU_PER_DOGE).integerValue(BigNumber.ROUND_DOWN).toNumber();
4019
+ } catch (error) {
4020
+ throw new Error(`toSatoshi failed: ${error.message}`);
4014
4021
  }
4015
- static getInstance(chainType, accountInfo, tomoAppInfo) {
4016
- if (!_EvmService.instance) {
4017
- _EvmService.instance = new _EvmService(chainType, accountInfo, tomoAppInfo);
4022
+ }
4023
+ function toBitcoin(satoshis) {
4024
+ try {
4025
+ const amount = new BigNumber(satoshis);
4026
+ if (amount.isNaN() || amount.isNegative()) {
4027
+ throw new Error("Invalid Koinu amount");
4018
4028
  }
4019
- return _EvmService.instance;
4029
+ return amount.dividedBy(KOINU_PER_DOGE).toNumber();
4030
+ } catch (error) {
4031
+ throw new Error(`toBitcoin failed: ${error.message}`);
4020
4032
  }
4021
- async eth_requestAccounts() {
4022
- const res = await this.eth_accounts();
4023
- return res;
4033
+ }
4034
+ function addUsedUtxos(newUsedUtxos) {
4035
+ const usedUtxos = cache.get("UsedUtxos") || {};
4036
+ for (const txid in newUsedUtxos) {
4037
+ usedUtxos[txid] = 1;
4024
4038
  }
4025
- async eth_accounts() {
4026
- const accounts = await this.accountInfo.getCurrent();
4027
- return accounts.map((account) => account.address);
4039
+ cache.set("UsedUtxos", usedUtxos, false);
4040
+ }
4041
+ function getUsedUtxos() {
4042
+ return cache.get("UsedUtxos") || {};
4043
+ }
4044
+ async function createPsbt({
4045
+ from,
4046
+ to,
4047
+ amount,
4048
+ fee,
4049
+ spendableUtxos
4050
+ }) {
4051
+ const utxos = spendableUtxos;
4052
+ if (!utxos || utxos.length === 0) {
4053
+ throw new Error("no spendable utxos");
4028
4054
  }
4029
- async eth_chainId() {
4030
- const chainType = this.chainType;
4031
- const currentNetwork = await this.getCurrentChain();
4032
- const { chainIdHex } = getAllTypeChainIds({
4033
- chainId: currentNetwork?.chainId,
4034
- chainType
4035
- });
4036
- return chainIdHex;
4055
+ const sendAmount = toSatoshi(amount);
4056
+ const sendCost = toSatoshi(fee);
4057
+ const totalNeeded = sendAmount + sendCost;
4058
+ const sortedUtxos = utxos.sort((a, b) => b.outputValue - a.outputValue);
4059
+ const selectedUtxos = [];
4060
+ let accumulatedAmount = 0;
4061
+ for (const utxo of sortedUtxos) {
4062
+ if (accumulatedAmount >= totalNeeded) break;
4063
+ selectedUtxos.push(utxo);
4064
+ accumulatedAmount += Number(utxo.outputValue);
4037
4065
  }
4038
- async getCurrentChain() {
4039
- this.networks.setChainType(this.chainType);
4040
- const currentNetwork = await this.networks.getCurrentNetwork();
4041
- return currentNetwork;
4066
+ if (accumulatedAmount < totalNeeded) {
4067
+ throw new Error("not enough funds to cover amount and fee");
4042
4068
  }
4043
- async wallet_switchEthereumChain(networks) {
4044
- const { chainId } = networks[0];
4045
- const isSupported = await this.isChainSupported(chainId);
4046
- if (!isSupported) {
4047
- throw new Error(`Chain ${chainId} is not supported`);
4069
+ const utxoDetailPromises = selectedUtxos.map(async (utxo) => {
4070
+ try {
4071
+ const utxoDetail = await getTxDetail(utxo.txid);
4072
+ return { utxo, utxoDetail };
4073
+ } catch (error) {
4074
+ return { utxo, utxoDetail: null };
4048
4075
  }
4049
- return await this.networks.setCurrentNetwork(chainId);
4050
- }
4051
- //evm chains
4052
- async isChainSupported(chainId) {
4053
- const chainType = this.chainType;
4054
- const res = getAllTypeChainIds({ chainId, chainType });
4055
- if (!res.chainId) {
4056
- return false;
4076
+ });
4077
+ const utxoResults = await Promise.all(utxoDetailPromises);
4078
+ const inputs = [];
4079
+ const usingUtxos = {};
4080
+ let addedAmount = 0;
4081
+ for (const { utxo, utxoDetail } of utxoResults) {
4082
+ if (addedAmount >= totalNeeded) break;
4083
+ const { txid, vout, outputValue, address = from } = utxo;
4084
+ if (utxoDetail?.hex && !usingUtxos[txid]) {
4085
+ inputs.push({
4086
+ txId: txid,
4087
+ vOut: vout,
4088
+ amount: Number(outputValue),
4089
+ nonWitnessUtxo: utxoDetail.hex,
4090
+ address
4091
+ });
4092
+ usingUtxos[txid] = 1;
4093
+ addedAmount += Number(outputValue);
4057
4094
  }
4058
- const chainInfo = await this.networks.getNetworkByChainId(res.chainId);
4059
- if (!chainInfo) {
4060
- return false;
4061
- }
4062
- return isEvmChain(chainInfo);
4063
4095
  }
4064
- async personal_sign([message, address]) {
4065
- const accounts = await this.accountInfo.getCurrent();
4066
- if (!isAddressEqual(accounts[0].address, address)) {
4067
- throw new Error("address is not the current account");
4068
- }
4069
- const signature = await this.accountInfo.signMessage(message);
4070
- return signature;
4096
+ if (addedAmount < totalNeeded) {
4097
+ throw new Error("not enough funds to cover amount and fee");
4071
4098
  }
4072
- async eth_signTypedData_v4([address, typedData]) {
4073
- const accounts = await this.accountInfo.getCurrent();
4074
- if (!isAddressEqual(accounts[0].address, address)) {
4075
- throw new Error("address is not the current account");
4099
+ const outputs = [
4100
+ {
4101
+ address: to,
4102
+ amount: sendAmount
4076
4103
  }
4077
- const signature = await this.accountInfo.signTypedData(typedData);
4078
- return signature;
4104
+ ];
4105
+ const changeAmount = addedAmount - sendAmount - sendCost;
4106
+ if (changeAmount > 0) {
4107
+ outputs.push({
4108
+ address: from,
4109
+ amount: changeAmount
4110
+ });
4079
4111
  }
4080
- async eth_getBalance([address, type]) {
4081
- const accounts = await this.accountInfo.getCurrent();
4082
- if (!isAddressEqual(accounts[0].address, address)) {
4083
- throw new Error("address is not the current account");
4084
- }
4085
- if (type !== "latest") {
4086
- throw new Error("type is not supported");
4087
- }
4088
- const chainInfo = await this.getCurrentChain();
4089
- const { rpcClient } = getRPCClient(chainInfo);
4090
- try {
4091
- const balance = await rpcClient.getBalance({ address });
4092
- return balance?.toString() || "0";
4093
- } catch (error) {
4094
- console.error(error);
4095
- return "0";
4112
+ const params = {
4113
+ address: from,
4114
+ inputs,
4115
+ outputs
4116
+ };
4117
+ const psbtBase64 = DogecoinUtils.buildPsbtToBase64(params);
4118
+ return { psbtBase64, usingUtxos };
4119
+ }
4120
+ function decodePsbt(psbt, type = "hex") {
4121
+ try {
4122
+ if (type === "base64") {
4123
+ const psbtHex = toHex(fromBase64(psbt));
4124
+ return Psbt.fromHex(psbtHex, {
4125
+ network
4126
+ });
4096
4127
  }
4128
+ return Psbt.fromHex(psbt, {
4129
+ network
4130
+ });
4131
+ } catch (error) {
4132
+ console.error("Failed to decode PSBT:", error);
4133
+ throw error;
4097
4134
  }
4098
- async _queryGasInfo(txData) {
4099
- const { tokenAddress, chainId, amount = 0, decimals = 9 } = txData;
4100
- const value = tokenAddress ? "0x1" : toHex(parseUnits(amount.toString(), decimals));
4101
- let callData = "0x";
4102
- if (tokenAddress) {
4103
- callData = createErc20TxData(txData, { decimals, address: tokenAddress })?.data;
4135
+ }
4136
+ var TransactionParser = class {
4137
+ rawTx;
4138
+ constructor(rawTx) {
4139
+ this.rawTx = rawTx;
4140
+ }
4141
+ hexToText(hex) {
4142
+ let str = "";
4143
+ for (let i = 0; i < hex.length; i += 2) {
4144
+ str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
4104
4145
  }
4105
- const gasLimitParam = {
4106
- from: txData.from,
4107
- to: txData.to,
4108
- value
4109
- };
4110
- const queryGasParams = {
4111
- chainIndex: Number(chainId),
4112
- callData,
4113
- gasLimitParam,
4114
- addressList: [txData.from]
4146
+ return str;
4147
+ }
4148
+ extractOPReturnData() {
4149
+ const ordIndex = this.rawTx.indexOf("6f7264");
4150
+ if (ordIndex === -1) return null;
4151
+ const dataHex = this.rawTx.substring(ordIndex);
4152
+ const dataText = this.hexToText(dataHex);
4153
+ const jsonMatch = dataText.match(/\{.*?\}/);
4154
+ return jsonMatch ? JSON.parse(jsonMatch[0]) : null;
4155
+ }
4156
+ parseScript() {
4157
+ return {
4158
+ version: this.rawTx.substring(0, 8),
4159
+ inputCount: parseInt(this.rawTx.substring(8, 10)),
4160
+ inputs: this.parseInputs(),
4161
+ opReturnData: this.extractOPReturnData()
4115
4162
  };
4116
- const {
4117
- data: gasInfo,
4118
- success,
4119
- message
4120
- } = await this.transactions.queryGasInfo({
4121
- chainType: this.chainType,
4122
- params: queryGasParams
4123
- });
4124
- if (!success) {
4125
- console.error("queryGasInfo evm", txData, queryGasParams, message, gasInfo);
4126
- const BaseConfig3 = SupportedChainTypes[ChainTypes.EVM];
4127
- const { gasFee } = BaseConfig3;
4128
- return {
4129
- success: true,
4130
- gasFee: gasFee.toString()
4131
- };
4132
- }
4133
- const nativeChainId = SupportedChainTypes[this.chainType].chainId;
4134
- const { nativeCurrency } = await this.networks.getNetworkByChainId(nativeChainId);
4135
- const { baseFee = 1, gasLimit = 0 } = gasInfo;
4136
- const baseFeeBN = new BigNumber(baseFee);
4137
- const calcFee = (feeLevel) => {
4138
- const fee = baseFeeBN.plus(gasInfo[feeLevel] || 0);
4139
- const gasFee = fee.times(gasLimit);
4140
- return formatUnits(BigInt(gasFee.toNumber()), nativeCurrency?.decimals || 9);
4163
+ }
4164
+ parseInputs() {
4165
+ const inputs = [];
4166
+ const position = 10;
4167
+ const firstInput = {
4168
+ txid: this.rawTx.substring(position, position + 64),
4169
+ vout: this.rawTx.substring(position + 64, position + 72),
4170
+ scriptData: this.extractOPReturnData()
4141
4171
  };
4172
+ inputs.push(firstInput);
4173
+ return inputs;
4174
+ }
4175
+ };
4176
+
4177
+ // src/dogecoin/rpc.ts
4178
+ var mydoge = axios.create({
4179
+ baseURL: MYDOGE_BASE_URL
4180
+ });
4181
+ var api = axios.create({
4182
+ baseURL: RPC_URL,
4183
+ timeout: RPC_TIMEOUT
4184
+ });
4185
+ async function getBalance(address) {
4186
+ if (!address) {
4142
4187
  return {
4143
- success,
4144
- fees: {
4145
- low: calcFee("priorityFeeLow"),
4146
- medium: calcFee("priorityFeeMedium"),
4147
- high: calcFee("priorityFeeHigh")
4148
- },
4149
- gasFee: calcFee("priorityFeeMedium"),
4150
- baseFee,
4151
- gasLimit,
4152
- priorityFee: {
4153
- low: gasInfo.priorityFeeLow,
4154
- medium: gasInfo.priorityFeeMedium,
4155
- high: gasInfo.priorityFeeHigh
4156
- }
4188
+ address: "",
4189
+ balance: 0
4157
4190
  };
4158
4191
  }
4159
- async createPublicClient({ chainId, rpcUrl }) {
4160
- if (rpcUrl) {
4161
- return createPublicClient({
4162
- transport: http(rpcUrl)
4163
- });
4164
- }
4165
- if (chainId) {
4166
- this.networks.setChainType(this.chainType);
4167
- const chainInfo = await this.networks.getNetworkByChainId(chainId);
4168
- rpcUrl = chainInfo.rpcUrls[0];
4169
- } else {
4170
- const chainInfo = await this.getCurrentChain();
4171
- rpcUrl = chainInfo.rpcUrls[0];
4172
- }
4173
- if (!rpcUrl) {
4174
- throw new Error("rpcUrl is not set");
4192
+ const path = `/address/${address}?page=1&pageSize=10`;
4193
+ const api2 = `${MYDOGE_BASE_URL}/wallet/info?route=` + encodeURIComponent(path);
4194
+ const res = await mydoge.get(api2);
4195
+ return res.data || {};
4196
+ }
4197
+ async function getTxDetail(txId) {
4198
+ if (!txId) {
4199
+ return {};
4200
+ }
4201
+ const path = `/tx/${txId}`;
4202
+ const api2 = `${MYDOGE_BASE_URL}/wallet/info?route=` + encodeURIComponent(path);
4203
+ const res = await mydoge.get(api2);
4204
+ return res.data || {};
4205
+ }
4206
+ async function getUtxos(address, cursor, result, filter, tx = null) {
4207
+ const query = (await mydoge.get(`${MYDOGE_BASE_URL}/utxos/${address}?filter=${filter}${cursor ? `&cursor=${cursor}` : ""}`)).data;
4208
+ let { utxos } = query;
4209
+ if (tx) {
4210
+ utxos = utxos.filter((utxo) => utxo.txid === tx?.txid && utxo.vout === tx?.vout);
4211
+ }
4212
+ result.push(
4213
+ ...utxos.map((i) => ({
4214
+ txid: i.txid,
4215
+ vout: i.vout,
4216
+ outputValue: i.satoshis,
4217
+ script: i.script_pubkey,
4218
+ ...filter === "inscriptions" && { inscriptions: i.inscriptions }
4219
+ }))
4220
+ );
4221
+ if (result.length && tx) {
4222
+ return;
4223
+ }
4224
+ result = result.sort((a, b) => toBitcoin(b.outputValue) - toBitcoin(a.outputValue));
4225
+ if (query.next_cursor) {
4226
+ return getUtxos(address, query.next_cursor, result, filter, tx);
4227
+ }
4228
+ }
4229
+ async function getInscriptionsUtxos(address) {
4230
+ const inscriptions = [];
4231
+ await getUtxos(address, 0, inscriptions, "inscriptions");
4232
+ return inscriptions;
4233
+ }
4234
+ async function getSpendableUtxos(address) {
4235
+ const utxos = [];
4236
+ await getUtxos(address, 0, utxos, "spendable");
4237
+ return utxos;
4238
+ }
4239
+ async function getUnSpentUtxos(address) {
4240
+ try {
4241
+ const result = await api.get(`/address/${address}/?unspent=true&limit=${0}`);
4242
+ const unSpentUtxo = result?.data;
4243
+ if (unSpentUtxo?.length) return unSpentUtxo.filter((utxo) => !utxo.spentTxid);
4244
+ return [];
4245
+ } catch (e) {
4246
+ return [];
4247
+ }
4248
+ }
4249
+ async function getInscriptionsUtxo(address, tx) {
4250
+ const inscriptions = [];
4251
+ await getUtxos(address, 0, inscriptions, "inscriptions", tx);
4252
+ return inscriptions[0];
4253
+ }
4254
+ async function sendTransaction({ signed, senderAddress }) {
4255
+ const jsonrpcReq = {
4256
+ jsonrpc: "2.0",
4257
+ id: `${senderAddress}_send_${Date.now()}`,
4258
+ method: "sendrawtransaction",
4259
+ params: [signed]
4260
+ };
4261
+ const jsonrpcRes = (await mydoge.post("/wallet/rpc", jsonrpcReq)).data;
4262
+ return jsonrpcRes;
4263
+ }
4264
+ async function estimateSmartFee({ senderAddress }) {
4265
+ const smartfeeReq = {
4266
+ jsonrpc: "2.0",
4267
+ id: `${senderAddress}_estimatesmartfee_${Date.now()}`,
4268
+ method: "estimatesmartfee",
4269
+ params: [BLOCK_CONFIRMATIONS]
4270
+ // confirm within x blocks
4271
+ };
4272
+ const feeData = (await mydoge.post("/wallet/rpc", smartfeeReq)).data;
4273
+ const feeRate = feeData?.result?.feerate || FEE_RATE_KB;
4274
+ const feePerKB = toSatoshi(feeRate * 2);
4275
+ return { feePerKB };
4276
+ }
4277
+ async function onCreateTransaction({ data, sendResponse }) {
4278
+ const amountSatoshi = toSatoshi(data.dogeAmount);
4279
+ const amount = toBitcoin(amountSatoshi);
4280
+ try {
4281
+ const response = await mydoge.post("/v3/tx/prepare", {
4282
+ sender: data.senderAddress,
4283
+ recipient: data.recipientAddress,
4284
+ amount
4285
+ });
4286
+ const { rawTx, fee, amount: resultAmount } = response.data;
4287
+ let amountMismatch = false;
4288
+ if (resultAmount < amount - fee) {
4289
+ amountMismatch = true;
4175
4290
  }
4176
- return createPublicClient({
4177
- transport: http(rpcUrl)
4291
+ sendResponse?.({
4292
+ rawTx,
4293
+ fee,
4294
+ amount: resultAmount,
4295
+ amountMismatch
4178
4296
  });
4297
+ } catch (err) {
4298
+ sendResponse?.(false);
4179
4299
  }
4180
- async eth_estimateGas(txs) {
4181
- const { from, to, value = "0x1", chainId } = txs[0] || {};
4182
- const accounts = await this.accountInfo.getCurrent();
4183
- if (accounts[0].address !== from) {
4184
- throw new Error("address is not the current account");
4185
- }
4186
- if (!to) {
4187
- throw new Error("to is not set");
4188
- }
4300
+ }
4301
+ async function getTransactions(address, config) {
4302
+ const { pageSize = 10, pageNumber = 1 } = config || {};
4303
+ const size = Math.min(pageSize, TRANSACTION_PAGE_SIZE);
4304
+ let txIds = [];
4305
+ let totalPages;
4306
+ let page;
4307
+ try {
4308
+ const response = (await mydoge.get("/wallet/info", {
4309
+ params: {
4310
+ route: `/address/${address}?page=${pageNumber}&pageSize=${size}`
4311
+ }
4312
+ })).data;
4313
+ txIds = response.txids;
4314
+ totalPages = response.totalPages;
4315
+ page = response.page;
4316
+ const transactions = await Promise.all(
4317
+ txIds?.map(async (txId) => {
4318
+ const detail = await getTxDetail(txId);
4319
+ const tx = new TransactionParser(detail.hex);
4320
+ const parsedData = tx.parseScript();
4321
+ return {
4322
+ ...detail,
4323
+ tick: parsedData?.opReturnData?.tick,
4324
+ tickAmount: parsedData?.opReturnData?.amt
4325
+ };
4326
+ }) || []
4327
+ );
4328
+ return { transactions, txIds, totalPages, page };
4329
+ } catch (err) {
4330
+ throw new Error("getTransactions failed");
4331
+ }
4332
+ }
4333
+ var getDogeFeeByBlock = async (address, n = 22) => {
4334
+ const res = await api.get(`/fee/${n}`);
4335
+ return (res.data?.feerate || 0.01) * TX_SIZE;
4336
+ };
4337
+ var sendDogeTx = async (rawTx) => {
4338
+ try {
4339
+ const res = await api.post("/tx/send", { rawTx });
4340
+ return res.data;
4341
+ } catch (e) {
4342
+ if (typeof e?.response?.data === "string") return Promise.reject(e?.response?.data);
4343
+ else return Promise.reject(e.message);
4344
+ }
4345
+ };
4346
+
4347
+ // src/dogecoin/utils-doge.ts
4348
+ var DogecoinAddress = class {
4349
+ static network = network;
4350
+ static generatePath(addressIndex) {
4351
+ return `m/44'/3'/0'/0/${addressIndex || 0}`;
4352
+ }
4353
+ static verifyAddress(address$1) {
4189
4354
  try {
4190
- const rpcClient = await this.createPublicClient({ chainId: chainId?.toString() });
4191
- const gas = await rpcClient.estimateGas({ account: from, to, value: hexToBigInt(value) });
4192
- return gas;
4355
+ const decoded = address.fromBase58Check(address$1);
4356
+ return decoded.version === network.pubKeyHash || decoded.version === network.scriptHash;
4193
4357
  } catch (error) {
4194
- console.warn("Failed to estimate gas:", error);
4195
- const BaseConfig3 = SupportedChainTypes[ChainTypes.EVM];
4196
- const { gasFee } = BaseConfig3;
4197
- return numberToHex(parseUnits(gasFee.toString(), 18));
4358
+ return false;
4198
4359
  }
4199
4360
  }
4200
- // Get nonce for current account
4201
- async eth_getTransactionCount([address, type]) {
4202
- const accounts = await this.accountInfo.getCurrent();
4203
- if (accounts[0].address !== address) {
4204
- throw new Error("address is not the current account");
4205
- }
4206
- if (type !== "latest" && type !== "pending") {
4207
- throw new Error("type is not supported");
4208
- }
4209
- try {
4210
- const rpcClient = await this.createPublicClient({});
4211
- const nonce = await rpcClient.getTransactionCount({ address });
4212
- return nonce;
4361
+ };
4362
+ var DogecoinUtils = class {
4363
+ /**
4364
+ * Build the PSBT from the transaction
4365
+ * @param tx utxoTx
4366
+ * @param _maximumFeeRate number (optional, currently unused)
4367
+ * @returns base64
4368
+ */
4369
+ static buildPsbtToBase64(tx, _maximumFeeRate) {
4370
+ const psbt = new Psbt({ network });
4371
+ for (const input of tx.inputs) {
4372
+ const inputOptions = {
4373
+ hash: input.txId,
4374
+ index: input.vOut
4375
+ };
4376
+ if (input.nonWitnessUtxo) {
4377
+ inputOptions.nonWitnessUtxo = Buffer.from(input.nonWitnessUtxo, "hex");
4378
+ }
4379
+ psbt.addInput(inputOptions);
4380
+ }
4381
+ for (const output of tx.outputs) {
4382
+ psbt.addOutput({
4383
+ value: BigInt(output?.amount || 0),
4384
+ address: output.address
4385
+ });
4386
+ }
4387
+ const psbtHex = psbt.toHex();
4388
+ return toBase64(fromHex(psbtHex));
4389
+ }
4390
+ /**
4391
+ * Extract the transaction from the signed PSBT
4392
+ * @param signedPsbt base64
4393
+ * @param _maximumFeeRate number (optional, currently unused)
4394
+ * @returns transaction hex
4395
+ */
4396
+ static extractPsbtTransaction(signedPsbt, _maximumFeeRate) {
4397
+ const signedPsbtHex = toHex(fromBase64(signedPsbt));
4398
+ const psbt = Psbt.fromHex(signedPsbtHex, {
4399
+ network
4400
+ });
4401
+ const maximumFeeRate = _maximumFeeRate || 1e5;
4402
+ psbt.setMaximumFeeRate(maximumFeeRate);
4403
+ psbt.finalizeAllInputs();
4404
+ const transaction = psbt.extractTransaction();
4405
+ return transaction.toHex();
4406
+ }
4407
+ /**
4408
+ * Convert raw transaction hex to PSBT base64
4409
+ * This method converts an unsigned raw transaction to PSBT format.
4410
+ * Note: The rawTx should be an unsigned transaction, and the nonWitnessUtxo
4411
+ * for each input will need to be fetched separately from the blockchain.
4412
+ * @param rawTx hex string of the raw transaction
4413
+ * @returns base64 string of the PSBT
4414
+ */
4415
+ static async rawTxToPsbtBase64(rawTx) {
4416
+ try {
4417
+ const cleanHex = rawTx.startsWith("0x") ? rawTx.slice(2) : rawTx;
4418
+ const transaction = Transaction.fromHex(cleanHex);
4419
+ const psbt = new Psbt({ network });
4420
+ for (let i = 0; i < transaction.ins.length; i++) {
4421
+ const input = transaction.ins[i];
4422
+ const hash = Buffer.from(input.hash).toString("hex");
4423
+ const index = input.index;
4424
+ const inputOptions = {
4425
+ hash,
4426
+ index
4427
+ };
4428
+ try {
4429
+ const prevTxDetail = await getTxDetail(hash);
4430
+ if (prevTxDetail?.hex) {
4431
+ inputOptions.nonWitnessUtxo = Buffer.from(prevTxDetail.hex, "hex");
4432
+ } else {
4433
+ throw new Error(`Previous transaction ${hash} has no hex data`);
4434
+ }
4435
+ } catch (error) {
4436
+ throw new Error(
4437
+ `Failed to fetch previous transaction ${hash} for input #${i}. This is required for PSBT creation. Error: ${error instanceof Error ? error.message : String(error)}`
4438
+ );
4439
+ }
4440
+ psbt.addInput(inputOptions);
4441
+ }
4442
+ for (const output of transaction.outs) {
4443
+ try {
4444
+ const address$1 = address.fromOutputScript(output.script, network);
4445
+ psbt.addOutput({
4446
+ address: address$1,
4447
+ value: BigInt(output?.value || 0)
4448
+ });
4449
+ } catch (error) {
4450
+ psbt.addOutput({
4451
+ script: output.script,
4452
+ value: BigInt(output?.value || 0)
4453
+ });
4454
+ }
4455
+ }
4456
+ const psbtHex = psbt.toHex();
4457
+ return toBase64(fromHex(psbtHex));
4213
4458
  } catch (error) {
4214
- console.error("Failed to get nonce:", error);
4215
- throw new Error(`Failed to get nonce: ${error}`);
4459
+ console.warn("rawTxToPsbtBase64 failed:", error);
4460
+ throw new Error(
4461
+ `Failed to convert raw transaction to PSBT: ${error instanceof Error ? error.message : String(error)}`
4462
+ );
4216
4463
  }
4217
4464
  }
4218
- //https://docs.metamask.io/snaps/reference/keyring-api/chain-methods/#eth_signtransaction
4219
- async eth_signTransaction(txParams) {
4220
- const preparedTxPropsType = {
4221
- chainId: "number",
4222
- to: "string",
4223
- value: "bigint",
4224
- data: "string",
4225
- nonce: "number",
4226
- maxPriorityFeePerGas: "bigint",
4227
- maxFeePerGas: "bigint",
4228
- gasPrice: "bigint",
4229
- gas: "bigint"
4230
- };
4231
- const txData = txParams?.[0];
4232
- txData.gas = txData?.gas || txData?.gasLimit;
4233
- txData.data = txData.data || "0x";
4234
- txData.value = txData.value || "0x";
4235
- txData.nonce = txData.nonce || "0x0";
4236
- if (!txData?.gas || !txData?.to || !txData?.chainId) {
4237
- throw new Error("gas or to or chainId is not set");
4465
+ };
4466
+ var DogecoinService = class _DogecoinService extends BaseService {
4467
+ static instance;
4468
+ chainType;
4469
+ rpcService;
4470
+ constructor(chainType, accountInfo, tomoAppInfo) {
4471
+ super(tomoAppInfo, accountInfo);
4472
+ this.chainType = chainType;
4473
+ this.rpcService = rpc_exports;
4474
+ }
4475
+ //singleton
4476
+ static getInstance(chainType, accountInfo, tomoAppInfo) {
4477
+ if (!_DogecoinService.instance) {
4478
+ _DogecoinService.instance = new _DogecoinService(chainType, accountInfo, tomoAppInfo);
4238
4479
  }
4480
+ return _DogecoinService.instance;
4481
+ }
4482
+ async requestAccounts() {
4483
+ const addresses = await this.accountInfo.getCurrent();
4484
+ const { publicKey, address } = addresses?.[0] || {};
4485
+ if (!address) {
4486
+ throw new Error("address is not set");
4487
+ }
4488
+ const { balance = 0 } = await getBalance(address);
4489
+ return {
4490
+ address,
4491
+ balance,
4492
+ approved: true,
4493
+ publicKey
4494
+ };
4495
+ }
4496
+ async getAccounts() {
4239
4497
  const accounts = await this.accountInfo.getCurrent();
4240
- const address = accounts[0].address;
4241
- if (txData?.from && !isAddressEqual(txData?.from, address)) {
4242
- console.error("eth_signTransaction error data: from is not the current account", txData);
4243
- throw new Error(`eth_signTransaction error data: from is not the current account`);
4498
+ return accounts.map((account) => account.address);
4499
+ }
4500
+ async getConnectionStatus() {
4501
+ const addresses = await this.accountInfo.getCurrent();
4502
+ const { address } = addresses?.[0] || {};
4503
+ if (!address) {
4504
+ throw new Error("address is not set");
4244
4505
  }
4245
- delete txData?.from;
4246
- delete txData?.gasLimit;
4247
- const preparedTx = {
4248
- gas: txData?.gas
4506
+ return {
4507
+ connected: true,
4508
+ address,
4509
+ selectedWalletAddress: address
4249
4510
  };
4250
- for (const key in preparedTxPropsType) {
4251
- if (txData?.[key] && isHex(txData?.[key])) {
4252
- preparedTx[key] = preparedTxPropsType[key] === "number" ? fromHex(txData[key], "number") : txData[key];
4253
- }
4511
+ }
4512
+ async getBalance() {
4513
+ const addresses = await this.accountInfo.getCurrent();
4514
+ const { address } = addresses?.[0] || {};
4515
+ if (!address) {
4516
+ throw new Error("address is not set");
4254
4517
  }
4255
- if (txData?.type !== "legacy") {
4256
- delete preparedTx.gasPrice;
4518
+ const { balance = 0 } = await getBalance(address);
4519
+ return {
4520
+ address,
4521
+ balance
4522
+ };
4523
+ }
4524
+ async signMessage({ message, type }) {
4525
+ if (type) {
4526
+ throw new Error("bip322simple not support.");
4257
4527
  }
4258
- try {
4259
- const serializedTransaction = await this.accountInfo.signTransaction(
4260
- JSON.stringify({
4261
- data: preparedTx,
4262
- types: preparedTxPropsType
4263
- })
4264
- );
4265
- if (serializedTransaction === "") {
4266
- throw new Error(`eth_signTransaction error data: ${JSON.stringify(txParams)}`);
4267
- }
4268
- const data = parseTransaction(serializedTransaction);
4269
- return data;
4270
- } catch (err) {
4271
- throw new Error("eth_signTransaction error" + err);
4528
+ const signature = await this.accountInfo.signMessage(message);
4529
+ return {
4530
+ signedMessage: signature
4531
+ };
4532
+ }
4533
+ async requestSignedMessage({
4534
+ message,
4535
+ type
4536
+ }) {
4537
+ return await this.signMessage({
4538
+ message,
4539
+ type
4540
+ });
4541
+ }
4542
+ async _queryGasInfo(txData) {
4543
+ const { chainId, amount = 0, decimals = 8 } = txData;
4544
+ const gasLimitParam = {
4545
+ from: txData.from,
4546
+ to: txData.to,
4547
+ value: toHex$1(parseUnits(amount.toString(), decimals))
4548
+ };
4549
+ const queryGasParams = {
4550
+ chainIndex: Number(chainId || BaseConfig.chainId),
4551
+ callData: "0x",
4552
+ gasLimitParam,
4553
+ addressList: [txData.from]
4554
+ };
4555
+ const {
4556
+ data: gasInfo,
4557
+ success,
4558
+ message
4559
+ } = await this.transactions.queryGasInfo({
4560
+ chainType: this.chainType,
4561
+ params: queryGasParams
4562
+ });
4563
+ if (!success) {
4564
+ console.error("queryGasInfo doge", success, txData, queryGasParams, gasInfo);
4565
+ const { gasFee } = BaseConfig;
4566
+ return {
4567
+ success: true,
4568
+ gasFee: gasFee.toString()
4569
+ };
4272
4570
  }
4571
+ const { baseFee = 1, gasLimit = 1 } = gasInfo;
4572
+ const calcFee = (feeLevel) => {
4573
+ const gasFee = new BigNumber(gasInfo[feeLevel] || baseFee).times(gasLimit);
4574
+ return toBitcoin(gasFee.toNumber());
4575
+ };
4576
+ const fees = {
4577
+ low: calcFee("priorityFeeLow"),
4578
+ medium: calcFee("priorityFeeMedium"),
4579
+ high: calcFee("priorityFeeHigh")
4580
+ };
4581
+ return {
4582
+ success: true,
4583
+ fees,
4584
+ gasFee: fees.high
4585
+ };
4273
4586
  }
4274
- async eth_sendRawTransaction([serializedTransaction], rpcUrl) {
4275
- try {
4276
- const rpcClient = await this.createPublicClient({ rpcUrl });
4277
- const hash = await rpcClient.sendRawTransaction({
4278
- serializedTransaction
4279
- });
4280
- return hash;
4281
- } catch (error) {
4282
- console.error("Failed to send transaction via RPC:", error, rpcUrl);
4283
- throw error;
4587
+ async requestPsbt({
4588
+ rawTx,
4589
+ signOnly
4590
+ }) {
4591
+ const psbtBase64 = await DogecoinUtils.rawTxToPsbtBase64(rawTx);
4592
+ const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify({ psbtBase64 }));
4593
+ if (!signedPsbt) {
4594
+ return null;
4595
+ }
4596
+ const signedRawTx = DogecoinUtils.extractPsbtTransaction(signedPsbt, 1e5);
4597
+ if (signOnly) {
4598
+ return {
4599
+ signedRawTx
4600
+ };
4284
4601
  }
4602
+ const { txid = "" } = await sendDogeTx(signedRawTx);
4603
+ return {
4604
+ txId: txid,
4605
+ signedRawTx
4606
+ };
4285
4607
  }
4286
- async getTransaction(hash) {
4287
- if (!hash || !isHex(hash)) {
4288
- throw new Error("txId is not valid");
4608
+ async requestTransaction(txData) {
4609
+ const spendableUtxos = txData?.spendableUtxos;
4610
+ if (txData.amountMismatch) {
4611
+ throw new Error("balance_insufficient");
4612
+ }
4613
+ if (!spendableUtxos || spendableUtxos?.length === 0) {
4614
+ txData.spendableUtxos = await getSpendableUtxos(txData.from);
4615
+ }
4616
+ const { psbtBase64, usingUtxos } = await createPsbt(txData);
4617
+ const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify({ psbtBase64 }));
4618
+ const signedTx = DogecoinUtils.extractPsbtTransaction(signedPsbt, 1e5);
4619
+ if (signedTx === "") {
4620
+ throw new Error("Error: sign transaction err.");
4289
4621
  }
4290
4622
  try {
4291
- const rpcClient = await this.createPublicClient({});
4292
- const tx = await rpcClient.getTransaction({
4293
- hash
4294
- });
4295
- return tx;
4296
- } catch (error) {
4297
- console.error("Failed to send transaction via RPC:", error, hash);
4298
- throw error;
4623
+ const { txid } = await sendDogeTx(signedTx);
4624
+ addUsedUtxos(usingUtxos);
4625
+ return { txId: txid };
4626
+ } catch (err) {
4627
+ throw new Error("Error: send transaction err." + JSON.stringify(err));
4299
4628
  }
4300
4629
  }
4301
- };
4302
- var BaseConfig = SupportedChainTypes[ChainTypes.SOL];
4303
- var TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
4304
- var MSG_PREFIX = "\xFFsolana offchain";
4305
-
4306
- // src/solana/utils.ts
4307
- __toESM(require_bn());
4308
- new PublicKey(TOKEN_METADATA_PROGRAM_ID);
4309
- var txToHex = (tx) => {
4310
- if (tx instanceof VersionedTransaction) {
4311
- return Buffer.from(tx.serialize()).toString("hex");
4312
- }
4313
- if (tx instanceof Transaction$1) {
4314
- return Buffer.from(
4315
- tx.serialize({
4316
- requireAllSignatures: false,
4317
- verifySignatures: false
4318
- })
4319
- ).toString("hex");
4630
+ async getTransactionStatus({ txId }) {
4631
+ const transaction = await getTxDetail(txId);
4632
+ if (!transaction?.txid) {
4633
+ return null;
4634
+ }
4635
+ return {
4636
+ txId: transaction.txid,
4637
+ confirmations: transaction.confirmations,
4638
+ status: transaction.confirmations > 0 ? "confirmed" : "pending",
4639
+ dogeAmount: transaction.vout[0]?.value,
4640
+ blockTime: transaction.blockTime,
4641
+ address: transaction.vout[0]?.addresses[0]
4642
+ };
4320
4643
  }
4321
- return tx;
4322
4644
  };
4323
- async function createLegacyTx({ from = "", to = "", amount = 0 }, connection) {
4324
- const fromPubkey = new PublicKey(from);
4325
- const toPubkey = new PublicKey(to);
4326
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
4327
- const transaction = new Transaction$1().add(
4328
- SystemProgram.transfer({
4329
- fromPubkey,
4330
- //payer
4331
- toPubkey,
4332
- //toAccount
4333
- lamports: amount * LAMPORTS_PER_SOL
4334
- })
4335
- );
4336
- transaction.feePayer = fromPubkey;
4337
- transaction.recentBlockhash = blockhash;
4338
- transaction.lastValidBlockHeight = lastValidBlockHeight;
4339
- return transaction;
4340
- }
4341
- async function createTokenLegacyTransaction2({
4342
- from = "",
4343
- to = "",
4344
- amount = 0,
4345
- tokenAddress = "",
4346
- priorityFee = 0,
4347
- // microLamports (1 SOL = 1e6 microLamports)
4348
- decimals = 6
4349
- }, connection) {
4350
- try {
4351
- const fromPubkey = new PublicKey(from);
4352
- const toPubkey = new PublicKey(to);
4353
- const tokenPublicKey = new PublicKey(tokenAddress);
4354
- const fromTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, fromPubkey);
4355
- const toTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, toPubkey);
4356
- const { blockhash } = await connection.getLatestBlockhash("confirmed");
4357
- const transaction = new Transaction$1();
4358
- transaction.feePayer = fromPubkey;
4359
- transaction.recentBlockhash = blockhash;
4360
- try {
4361
- const instruction = createAssociatedTokenAccountInstruction(fromPubkey, toTokenPubKey, toPubkey, tokenPublicKey);
4362
- transaction.add(instruction);
4363
- } catch (error) {
4364
- console.error(error);
4365
- throw error;
4645
+ function getRPCClient(network2) {
4646
+ const { chainId, name, rpcUrls, nativeCurrencyDecimals, nativeCurrencyName, nativeCurrencySymbol } = network2;
4647
+ const myCustomChain = {
4648
+ id: Number(chainId) || chainId,
4649
+ name,
4650
+ nativeCurrency: {
4651
+ name: nativeCurrencyName,
4652
+ symbol: nativeCurrencySymbol,
4653
+ decimals: nativeCurrencyDecimals
4654
+ },
4655
+ rpcUrls: {
4656
+ default: {
4657
+ http: rpcUrls,
4658
+ webSocket: []
4659
+ },
4660
+ public: {
4661
+ http: rpcUrls,
4662
+ webSocket: []
4663
+ }
4664
+ },
4665
+ blockExplorers: {
4666
+ default: {
4667
+ name: "Explorer",
4668
+ url: rpcUrls[0]
4669
+ }
4366
4670
  }
4367
- const tokenAmount = new Bignumber(amount).multipliedBy(new Bignumber(10).pow(decimals)).toNumber();
4368
- transaction.add(
4369
- createTransferInstruction(fromTokenPubKey, toTokenPubKey, fromPubkey, tokenAmount, [], TOKEN_PROGRAM_ID)
4370
- );
4371
- return transaction;
4372
- } catch (error) {
4373
- console.error("Error creating transaction:", error);
4374
- throw error;
4375
- }
4671
+ };
4672
+ const rpcClient = createPublicClient({
4673
+ chain: myCustomChain,
4674
+ pollingInterval: 1e4,
4675
+ cacheTime: 1e4,
4676
+ transport: http()
4677
+ });
4678
+ return { rpcClient };
4376
4679
  }
4377
4680
 
4378
- // src/dogecoin/base.ts
4379
- function fromHex3(hex) {
4380
- const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
4381
- const paddedHex = cleanHex.length % 2 === 0 ? cleanHex : "0" + cleanHex;
4382
- const bytes = new Uint8Array(paddedHex.length / 2);
4383
- for (let i = 0; i < paddedHex.length; i += 2) {
4384
- bytes[i / 2] = parseInt(paddedHex.substr(i, 2), 16);
4385
- }
4386
- return bytes;
4387
- }
4388
- function toHex3(data) {
4389
- if (typeof data === "string") {
4390
- return data.startsWith("0x") ? data.slice(2) : data;
4391
- }
4392
- const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
4393
- return buffer.toString("hex");
4394
- }
4395
- function toBase64(data) {
4396
- if (typeof data === "string") {
4397
- const buffer2 = Buffer.from(data, "hex");
4398
- return buffer2.toString("base64");
4681
+ // src/types/account.ts
4682
+ var AccountType = /* @__PURE__ */ ((AccountType2) => {
4683
+ AccountType2["EOA"] = "EOA";
4684
+ AccountType2["SOCIAL"] = "SOCIAL";
4685
+ return AccountType2;
4686
+ })(AccountType || {});
4687
+
4688
+ // src/types/wallet.ts
4689
+ var TxTypes = /* @__PURE__ */ ((TxTypes2) => {
4690
+ TxTypes2[TxTypes2["swap"] = 1] = "swap";
4691
+ TxTypes2[TxTypes2["bridge"] = 2] = "bridge";
4692
+ TxTypes2[TxTypes2["receive"] = 31] = "receive";
4693
+ TxTypes2[TxTypes2["send"] = 32] = "send";
4694
+ TxTypes2[TxTypes2["approve"] = 4] = "approve";
4695
+ TxTypes2[TxTypes2["contractInteraction"] = 5] = "contractInteraction";
4696
+ TxTypes2[TxTypes2["redPocket"] = 6] = "redPocket";
4697
+ return TxTypes2;
4698
+ })(TxTypes || {});
4699
+
4700
+ // src/evm/utils.ts
4701
+ var isEvmChain = (network2) => {
4702
+ return network2 && network2?.platformType === "EVM";
4703
+ };
4704
+ var getAllTypeChainIds = ({ chainId, chainType }) => {
4705
+ if (isHex(chainId)) {
4706
+ const chainIdHex2 = chainId;
4707
+ chainId = fromHex$1(chainId, "number").toString();
4708
+ return {
4709
+ chainId,
4710
+ chainIdHex: chainIdHex2,
4711
+ chainUid: `${chainType}:${chainId}`
4712
+ };
4399
4713
  }
4400
- const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
4401
- return buffer.toString("base64");
4402
- }
4403
- function fromBase64(base64) {
4404
- const buffer = Buffer.from(base64, "base64");
4405
- return new Uint8Array(buffer);
4406
- }
4407
- function toBase58(data) {
4408
- throw new Error("toBase58 requires bs58 package. Install it: pnpm add bs58");
4714
+ const chainIdHex = toHex$1(Number(chainId));
4715
+ return {
4716
+ chainId,
4717
+ chainIdHex,
4718
+ chainUid: `${chainType}:${chainId}`
4719
+ };
4720
+ };
4721
+ function createErc20TxData(params, token) {
4722
+ const { decimals, address: tokenAddress } = token;
4723
+ const value = parseUnits(params?.amount.toString(), decimals);
4724
+ const callData = encodeFunctionData({
4725
+ abi: erc20Abi,
4726
+ functionName: "transfer",
4727
+ args: [params.to, value]
4728
+ });
4729
+ return {
4730
+ ...params,
4731
+ ...{ amount: 0, data: callData, to: tokenAddress }
4732
+ };
4409
4733
  }
4410
4734
 
4411
- // src/solana/service.ts
4412
- var SolanaService = class _SolanaService extends BaseService {
4735
+ // src/evm/service.ts
4736
+ var EvmService = class _EvmService extends BaseService {
4413
4737
  static instance;
4414
4738
  chainType;
4415
- rpcUrl;
4416
- connection;
4417
4739
  constructor(chainType, accountInfo, tomoAppInfo) {
4418
4740
  super(tomoAppInfo, accountInfo);
4419
4741
  this.chainType = chainType;
4420
- const config = {
4421
- commitment: "confirmed",
4422
- disableRetryOnRateLimit: false,
4423
- confirmTransactionInitialTimeout: 12e4,
4424
- fetch: (url, options) => {
4425
- return fetch(url, { ...options });
4426
- }
4427
- };
4428
- const domain = TomoApiDomains[tomoAppInfo.tomoStage];
4429
- const rpcUrl = `${domain}/rpc/v1/solana`;
4430
- this.rpcUrl = rpcUrl;
4431
- this.connection = new Connection(rpcUrl, config);
4432
4742
  }
4433
4743
  static getInstance(chainType, accountInfo, tomoAppInfo) {
4434
- if (!_SolanaService.instance) {
4435
- _SolanaService.instance = new _SolanaService(chainType, accountInfo, tomoAppInfo);
4744
+ if (!_EvmService.instance) {
4745
+ _EvmService.instance = new _EvmService(chainType, accountInfo, tomoAppInfo);
4436
4746
  }
4437
- return _SolanaService.instance;
4747
+ return _EvmService.instance;
4438
4748
  }
4439
- async getCurrentWalletAccount() {
4440
- return await this.accountInfo.getCurrent();
4749
+ async eth_requestAccounts() {
4750
+ const res = await this.eth_accounts();
4751
+ return res;
4441
4752
  }
4442
- async getAccount() {
4443
- const { address: currentAddress, publicKey } = await this.accountInfo.getCurrent();
4444
- return { publicKey: publicKey || currentAddress, address: currentAddress };
4753
+ async eth_accounts() {
4754
+ const accounts = await this.accountInfo.getCurrent();
4755
+ return accounts.map((account) => account.address);
4445
4756
  }
4446
- async signMessage(params) {
4447
- const { address: currentAddress, publicKey } = await this.accountInfo.getCurrent();
4448
- const { message } = params;
4757
+ async eth_chainId() {
4758
+ const chainType = this.chainType;
4759
+ const currentNetwork = await this.getCurrentChain();
4760
+ const { chainIdHex } = getAllTypeChainIds({
4761
+ chainId: currentNetwork?.chainId,
4762
+ chainType
4763
+ });
4764
+ return chainIdHex;
4765
+ }
4766
+ async getCurrentChain() {
4767
+ this.networks.setChainType(this.chainType);
4768
+ const currentNetwork = await this.networks.getCurrentNetwork();
4769
+ return currentNetwork;
4770
+ }
4771
+ async wallet_switchEthereumChain(networks) {
4772
+ const { chainId } = networks[0];
4773
+ const isSupported = await this.isChainSupported(chainId);
4774
+ if (!isSupported) {
4775
+ throw new Error(`Chain ${chainId} is not supported`);
4776
+ }
4777
+ return await this.networks.setCurrentNetwork(chainId);
4778
+ }
4779
+ //evm chains
4780
+ async isChainSupported(chainId) {
4781
+ const chainType = this.chainType;
4782
+ const res = getAllTypeChainIds({ chainId, chainType });
4783
+ if (!res.chainId) {
4784
+ return false;
4785
+ }
4786
+ const chainInfo = await this.networks.getNetworkByChainId(res.chainId);
4787
+ if (!chainInfo) {
4788
+ return false;
4789
+ }
4790
+ return isEvmChain(chainInfo);
4791
+ }
4792
+ async personal_sign([message, address]) {
4793
+ const accounts = await this.accountInfo.getCurrent();
4794
+ if (!isAddressEqual(accounts[0].address, address)) {
4795
+ throw new Error("address is not the current account");
4796
+ }
4449
4797
  const signature = await this.accountInfo.signMessage(message);
4450
- return {
4451
- message: MSG_PREFIX + message,
4452
- signature,
4453
- publicKey: publicKey || currentAddress
4454
- };
4798
+ return signature;
4455
4799
  }
4456
- async signIn(params) {
4457
- const { address: currentAddress, publicKey } = await this.accountInfo.getCurrent();
4458
- const { signature = "" } = await this.signMessage(params);
4459
- return {
4460
- address: currentAddress,
4461
- publicKey: publicKey || currentAddress,
4462
- signature,
4463
- signedMessage: MSG_PREFIX + params.message
4464
- };
4800
+ async eth_signTypedData_v4([address, typedData]) {
4801
+ const accounts = await this.accountInfo.getCurrent();
4802
+ if (!isAddressEqual(accounts[0].address, address)) {
4803
+ throw new Error("address is not the current account");
4804
+ }
4805
+ const signature = await this.accountInfo.signTypedData(typedData);
4806
+ return signature;
4465
4807
  }
4466
- async request({ method, params }) {
4467
- if (!this[method]) {
4468
- throw new Error(`${method} in request is not supported`);
4808
+ async eth_getBalance([address, type]) {
4809
+ const accounts = await this.accountInfo.getCurrent();
4810
+ if (!isAddressEqual(accounts[0].address, address)) {
4811
+ throw new Error("address is not the current account");
4812
+ }
4813
+ if (type !== "latest") {
4814
+ throw new Error("type is not supported");
4815
+ }
4816
+ const chainInfo = await this.getCurrentChain();
4817
+ const { rpcClient } = getRPCClient(chainInfo);
4818
+ try {
4819
+ const balance = await rpcClient.getBalance({ address });
4820
+ return balance?.toString() || "0";
4821
+ } catch (error) {
4822
+ console.error(error);
4823
+ return "0";
4469
4824
  }
4470
- return this[method](params);
4471
4825
  }
4472
4826
  async _queryGasInfo(txData) {
4473
4827
  const { tokenAddress, chainId, amount = 0, decimals = 9 } = txData;
4474
- const value = tokenAddress ? "0x1" : toHex(parseUnits(amount.toString(), decimals));
4828
+ const value = tokenAddress ? "0x1" : toHex$1(parseUnits(amount.toString(), decimals));
4475
4829
  let callData = "0x";
4476
4830
  if (tokenAddress) {
4477
- const transaction = await createTokenLegacyTransaction2(txData, this.connection);
4478
- callData = "0x" + txToHex(transaction);
4479
- } else {
4480
- const transaction = await createLegacyTx(txData, this.connection);
4481
- callData = "0x" + txToHex(transaction);
4831
+ callData = createErc20TxData(txData, { decimals, address: tokenAddress })?.data;
4482
4832
  }
4483
4833
  const gasLimitParam = {
4484
4834
  from: txData.from,
@@ -4500,8 +4850,9 @@ var SolanaService = class _SolanaService extends BaseService {
4500
4850
  params: queryGasParams
4501
4851
  });
4502
4852
  if (!success) {
4503
- console.error("queryGasInfo solana", txData, message, gasInfo);
4504
- const { gasFee } = BaseConfig;
4853
+ console.error("queryGasInfo evm", txData, queryGasParams, message, gasInfo);
4854
+ const BaseConfig3 = SupportedChainTypes[ChainTypes.EVM];
4855
+ const { gasFee } = BaseConfig3;
4505
4856
  return {
4506
4857
  success: true,
4507
4858
  gasFee: gasFee.toString()
@@ -4510,528 +4861,326 @@ var SolanaService = class _SolanaService extends BaseService {
4510
4861
  const nativeChainId = SupportedChainTypes[this.chainType].chainId;
4511
4862
  const { nativeCurrency } = await this.networks.getNetworkByChainId(nativeChainId);
4512
4863
  const { baseFee = 1, gasLimit = 0 } = gasInfo;
4513
- const baseFeeBN = new BigNumber(baseFee).times(2);
4514
- const gasLimitBN = new BigNumber(gasLimit);
4864
+ const baseFeeBN = new BigNumber(baseFee);
4515
4865
  const calcFee = (feeLevel) => {
4516
- const priorityFee = gasLimitBN.times(gasInfo[feeLevel] || 0).dividedBy(1e6).integerValue(BigNumber.ROUND_DOWN);
4517
- const gasFee = baseFeeBN.plus(priorityFee);
4866
+ const fee = baseFeeBN.plus(gasInfo[feeLevel] || 0);
4867
+ const gasFee = fee.times(gasLimit);
4518
4868
  return formatUnits(BigInt(gasFee.toNumber()), nativeCurrency?.decimals || 9);
4519
4869
  };
4520
4870
  return {
4521
- success: true,
4871
+ success,
4522
4872
  fees: {
4523
4873
  low: calcFee("priorityFeeLow"),
4524
4874
  medium: calcFee("priorityFeeMedium"),
4525
4875
  high: calcFee("priorityFeeHigh")
4526
- },
4527
- gasFee: calcFee("priorityFeeHigh")
4528
- };
4529
- }
4530
- async sendSignedTx(signedTx) {
4531
- let rawTx;
4532
- try {
4533
- const hexBuffer = Buffer.from(signedTx, "hex");
4534
- VersionedTransaction.deserialize(hexBuffer);
4535
- rawTx = hexBuffer;
4536
- } catch (error) {
4537
- const base58Buffer = Buffer.from(toBase58());
4538
- VersionedTransaction.deserialize(base58Buffer);
4539
- rawTx = base58Buffer;
4540
- }
4541
- const transaction = VersionedTransaction.deserialize(rawTx);
4542
- if (!transaction.signatures || transaction.signatures.length === 0) {
4543
- throw new Error("lack of sign");
4544
- }
4545
- const confirmationStrategy = {
4546
- commitment: "confirmed",
4547
- preflightCommitment: "processed",
4548
- skipPreflight: false,
4549
- maxRetries: 5
4550
- };
4551
- try {
4552
- const simulation = await this.connection.simulateTransaction(transaction);
4553
- if (simulation.value.err) {
4554
- const logs = simulation.value.logs || [];
4555
- const message = logs[logs.length - 1] || simulation.value.err || "Unknown error occurred";
4556
- console.error("send err logs:", logs, message);
4557
- throw new Error(`Transaction failed: ${message}`);
4558
- }
4559
- const signature = await sendAndConfirmRawTransaction(this.connection, rawTx, confirmationStrategy);
4560
- return signature;
4561
- } catch (error) {
4562
- if (error.message.includes("TransactionExpiredTimeoutError")) {
4563
- const status = await this.connection.getSignatureStatus(transaction.signatures[0].toString());
4564
- if (status.value?.confirmationStatus === "confirmed") {
4565
- return transaction.signatures[0].toString();
4566
- }
4567
- }
4568
- throw error;
4569
- }
4570
- }
4571
- async signTransaction({ rawTransaction }) {
4572
- const params = {
4573
- rawTransaction,
4574
- walletId: -1,
4575
- rpcUrl: this.rpcUrl
4576
- };
4577
- const signature = await this.accountInfo.signTransaction(JSON.stringify(params));
4578
- return signature;
4579
- }
4580
- async signAndSendTransaction({ rawTransaction }) {
4581
- const signedTx = await this.signTransaction({ rawTransaction });
4582
- if (!signedTx) {
4583
- return "";
4584
- }
4585
- const signature = await this.sendSignedTx(signedTx);
4586
- return signature;
4587
- }
4588
- async signAllTransactions({ rawTransactions }) {
4589
- throw new Error("no support");
4590
- }
4591
- async signAndSendAllTransactions({ rawTransactions }) {
4592
- throw new Error("no support");
4593
- }
4594
- async queryRent(params) {
4595
- const MIN_ACCOUNT_ACTIVATION_SOL = "0.0009";
4596
- const SPL_TOKEN_ACCOUNT_CREATION_FEE = "0.001";
4597
- try {
4598
- const { toAddress, tokenAddress } = params;
4599
- let minTransferAmount = "0";
4600
- let createSplTokenFee = null;
4601
- if (tokenAddress) {
4602
- const tokenAccountExists = await this.checkTokenAccount(toAddress, tokenAddress);
4603
- if (!tokenAccountExists) {
4604
- createSplTokenFee = SPL_TOKEN_ACCOUNT_CREATION_FEE;
4605
- }
4606
- } else {
4607
- const accountExists = await this.checkAccountExists(toAddress);
4608
- if (!accountExists) {
4609
- minTransferAmount = MIN_ACCOUNT_ACTIVATION_SOL;
4610
- }
4611
- }
4612
- const rentInfo = {
4613
- minTransferAmount,
4614
- createSplTokenFee
4615
- };
4616
- return rentInfo;
4617
- } catch (error) {
4618
- console.error("queryRent error:", error);
4619
- const defaultRentInfo = {
4620
- minTransferAmount: "0",
4621
- createSplTokenFee: null
4622
- };
4623
- return defaultRentInfo;
4624
- }
4876
+ },
4877
+ gasFee: calcFee("priorityFeeMedium"),
4878
+ baseFee,
4879
+ gasLimit,
4880
+ priorityFee: {
4881
+ low: gasInfo.priorityFeeLow,
4882
+ medium: gasInfo.priorityFeeMedium,
4883
+ high: gasInfo.priorityFeeHigh
4884
+ }
4885
+ };
4625
4886
  }
4626
- async checkAccountExists(address) {
4627
- try {
4628
- const accountInfo = await this.connection.getAccountInfo(new PublicKey(address));
4629
- return accountInfo !== null;
4630
- } catch (error) {
4631
- console.warn("Failed to check account exists:", error);
4632
- return false;
4887
+ async createPublicClient({ chainId, rpcUrl }) {
4888
+ if (rpcUrl) {
4889
+ return createPublicClient({
4890
+ transport: http(rpcUrl)
4891
+ });
4892
+ }
4893
+ if (chainId) {
4894
+ this.networks.setChainType(this.chainType);
4895
+ const chainInfo = await this.networks.getNetworkByChainId(chainId);
4896
+ rpcUrl = chainInfo.rpcUrls[0];
4897
+ } else {
4898
+ const chainInfo = await this.getCurrentChain();
4899
+ rpcUrl = chainInfo.rpcUrls[0];
4900
+ }
4901
+ if (!rpcUrl) {
4902
+ throw new Error("rpcUrl is not set");
4633
4903
  }
4904
+ return createPublicClient({
4905
+ transport: http(rpcUrl)
4906
+ });
4634
4907
  }
4635
- async checkTokenAccount(ownerAddress, tokenMint) {
4908
+ async eth_estimateGas(txs) {
4909
+ const { from, to, value = "0x1", chainId } = txs[0] || {};
4910
+ const accounts = await this.accountInfo.getCurrent();
4911
+ if (accounts[0].address !== from) {
4912
+ throw new Error("address is not the current account");
4913
+ }
4914
+ if (!to) {
4915
+ throw new Error("to is not set");
4916
+ }
4636
4917
  try {
4637
- const owner = new PublicKey(ownerAddress);
4638
- const mint = new PublicKey(tokenMint);
4639
- const associatedTokenAddress = await getAssociatedTokenAddress(mint, owner);
4640
- const accountInfo = await this.connection.getAccountInfo(associatedTokenAddress);
4641
- return accountInfo !== null;
4918
+ const rpcClient = await this.createPublicClient({ chainId: chainId?.toString() });
4919
+ const gas = await rpcClient.estimateGas({ account: from, to, value: hexToBigInt(value) });
4920
+ return gas;
4642
4921
  } catch (error) {
4643
- console.warn("Failed to check token account:", error);
4644
- return false;
4922
+ console.warn("Failed to estimate gas:", error);
4923
+ const BaseConfig3 = SupportedChainTypes[ChainTypes.EVM];
4924
+ const { gasFee } = BaseConfig3;
4925
+ return numberToHex(parseUnits(gasFee.toString(), 18));
4645
4926
  }
4646
4927
  }
4647
- };
4648
- var BaseConfig2 = SupportedChainTypes[ChainTypes.DOGE];
4649
- var DECIMALS = 1e8;
4650
- var MYDOGE_BASE_URL = "https://api.mydoge.com";
4651
- var RPC_URL = "https://api.bitcore.io/api/DOGE/mainnet";
4652
- var RPC_TIMEOUT = 20 * 1e3;
4653
- var network = {
4654
- messagePrefix: "Dogecoin Signed Message:\n",
4655
- bech32: "dc",
4656
- bip44: 3,
4657
- bip32: {
4658
- public: 49990397,
4659
- private: 49988504
4660
- },
4661
- pubKeyHash: 30,
4662
- scriptHash: 22,
4663
- wif: 158
4664
- };
4665
-
4666
- // src/dogecoin/utils-doge.ts
4667
- var DogecoinUtils = class {
4668
- /**
4669
- * Build the PSBT from the transaction
4670
- * @param tx utxoTx
4671
- * @param _maximumFeeRate number (optional, currently unused)
4672
- * @returns base64
4673
- */
4674
- static buildPsbtToBase64(tx, _maximumFeeRate) {
4675
- const psbt = new Psbt({ network });
4676
- for (const input of tx.inputs) {
4677
- const inputOptions = {
4678
- hash: input.txId,
4679
- index: input.vOut
4680
- };
4681
- if (input.nonWitnessUtxo) {
4682
- inputOptions.nonWitnessUtxo = Buffer.from(input.nonWitnessUtxo, "hex");
4683
- }
4684
- psbt.addInput(inputOptions);
4928
+ // Get nonce for current account
4929
+ async eth_getTransactionCount([address, type]) {
4930
+ const accounts = await this.accountInfo.getCurrent();
4931
+ if (accounts[0].address !== address) {
4932
+ throw new Error("address is not the current account");
4685
4933
  }
4686
- for (const output of tx.outputs) {
4687
- psbt.addOutput({
4688
- value: output.amount,
4689
- address: output.address
4690
- });
4934
+ if (type !== "latest" && type !== "pending") {
4935
+ throw new Error("type is not supported");
4691
4936
  }
4692
- const psbtHex = psbt.toHex();
4693
- return toBase64(fromHex3(psbtHex));
4694
- }
4695
- /**
4696
- * Extract the transaction from the signed PSBT
4697
- * @param signedPsbt base64
4698
- * @param _maximumFeeRate number (optional, currently unused)
4699
- * @returns transaction hex
4700
- */
4701
- static extractPsbtTransaction(signedPsbt, _maximumFeeRate) {
4702
- const signedPsbtHex = toHex3(fromBase64(signedPsbt));
4703
- const psbt = Psbt.fromHex(signedPsbtHex, {
4704
- network
4705
- });
4706
- const maximumFeeRate = _maximumFeeRate || 1e5;
4707
- psbt.setMaximumFeeRate(maximumFeeRate);
4708
- psbt.finalizeAllInputs();
4709
- const transaction = psbt.extractTransaction();
4710
- return transaction.toHex();
4711
- }
4712
- /**
4713
- * Convert raw transaction hex to PSBT base64
4714
- * This method converts an unsigned raw transaction to PSBT format.
4715
- * Note: The rawTx should be an unsigned transaction, and the nonWitnessUtxo
4716
- * for each input will need to be fetched separately from the blockchain.
4717
- * @param rawTx hex string of the raw transaction
4718
- * @returns base64 string of the PSBT
4719
- */
4720
- static async rawTxToPsbtBase64(rawTx) {
4721
4937
  try {
4722
- const cleanHex = rawTx.startsWith("0x") ? rawTx.slice(2) : rawTx;
4723
- const transaction = Transaction.fromHex(cleanHex);
4724
- const psbt = new Psbt({ network });
4725
- for (let i = 0; i < transaction.ins.length; i++) {
4726
- const input = transaction.ins[i];
4727
- const hash = Buffer.from(input.hash).reverse().toString("hex");
4728
- const index = input.index;
4729
- const inputOptions = {
4730
- hash,
4731
- index,
4732
- nonWitnessUtxo: input.script
4733
- };
4734
- psbt.addInput(inputOptions);
4735
- }
4736
- for (const output of transaction.outs) {
4737
- try {
4738
- const address$1 = address.fromOutputScript(output.script, network);
4739
- psbt.addOutput({
4740
- address: address$1,
4741
- value: Number(output.value)
4742
- });
4743
- } catch (error) {
4744
- psbt.addOutput({
4745
- script: output.script,
4746
- value: Number(output.value)
4747
- });
4748
- }
4749
- }
4750
- const psbtHex = psbt.toHex();
4751
- return toBase64(fromHex3(psbtHex));
4938
+ const rpcClient = await this.createPublicClient({});
4939
+ const nonce = await rpcClient.getTransactionCount({ address });
4940
+ return nonce;
4752
4941
  } catch (error) {
4753
- console.warn("rawTxToPsbtBase64 failed:", error);
4754
- throw new Error(
4755
- `Failed to convert raw transaction to PSBT: ${error instanceof Error ? error.message : String(error)}`
4756
- );
4942
+ console.error("Failed to get nonce:", error);
4943
+ throw new Error(`Failed to get nonce: ${error}`);
4757
4944
  }
4758
4945
  }
4759
- };
4760
- var KOINU_PER_DOGE = new BigNumber(DECIMALS);
4761
- function toSatoshi(bitcion) {
4762
- try {
4763
- const amount = new BigNumber(bitcion);
4764
- if (amount.isNaN() || amount.isNegative()) {
4765
- throw new Error("Invalid amount");
4946
+ //https://docs.metamask.io/snaps/reference/keyring-api/chain-methods/#eth_signtransaction
4947
+ async eth_signTransaction(txParams) {
4948
+ const preparedTxPropsType = {
4949
+ chainId: "number",
4950
+ to: "string",
4951
+ value: "bigint",
4952
+ data: "string",
4953
+ nonce: "number",
4954
+ maxPriorityFeePerGas: "bigint",
4955
+ maxFeePerGas: "bigint",
4956
+ gasPrice: "bigint",
4957
+ gas: "bigint"
4958
+ };
4959
+ const txData = txParams?.[0];
4960
+ txData.gas = txData?.gas || txData?.gasLimit;
4961
+ txData.data = txData.data || "0x";
4962
+ txData.value = txData.value || "0x";
4963
+ txData.nonce = txData.nonce || "0x0";
4964
+ if (!txData?.gas || !txData?.to || !txData?.chainId) {
4965
+ throw new Error("gas or to or chainId is not set");
4766
4966
  }
4767
- return amount.times(KOINU_PER_DOGE).integerValue(BigNumber.ROUND_DOWN).toNumber();
4768
- } catch (error) {
4769
- throw new Error(`toSatoshi failed: ${error.message}`);
4770
- }
4771
- }
4772
- function toBitcoin(satoshis) {
4773
- try {
4774
- const amount = new BigNumber(satoshis);
4775
- if (amount.isNaN() || amount.isNegative()) {
4776
- throw new Error("Invalid Koinu amount");
4967
+ const accounts = await this.accountInfo.getCurrent();
4968
+ const address = accounts[0].address;
4969
+ if (txData?.from && !isAddressEqual(txData?.from, address)) {
4970
+ console.error("eth_signTransaction error data: from is not the current account", txData);
4971
+ throw new Error(`eth_signTransaction error data: from is not the current account`);
4972
+ }
4973
+ delete txData?.from;
4974
+ delete txData?.gasLimit;
4975
+ const preparedTx = {
4976
+ gas: txData?.gas
4977
+ };
4978
+ for (const key in preparedTxPropsType) {
4979
+ if (txData?.[key] && isHex(txData?.[key])) {
4980
+ preparedTx[key] = preparedTxPropsType[key] === "number" ? fromHex$1(txData[key], "number") : txData[key];
4981
+ }
4982
+ }
4983
+ if (txData?.type !== "legacy") {
4984
+ delete preparedTx.gasPrice;
4777
4985
  }
4778
- return amount.dividedBy(KOINU_PER_DOGE).toNumber();
4779
- } catch (error) {
4780
- throw new Error(`toBitcoin failed: ${error.message}`);
4781
- }
4782
- }
4783
- function addUsedUtxos(newUsedUtxos) {
4784
- const usedUtxos = cache.get("UsedUtxos") || {};
4785
- for (const txid in newUsedUtxos) {
4786
- usedUtxos[txid] = 1;
4787
- }
4788
- cache.set("UsedUtxos", usedUtxos, false);
4789
- }
4790
- async function createPsbt({
4791
- from,
4792
- to,
4793
- amount,
4794
- fee,
4795
- spendableUtxos
4796
- }) {
4797
- const utxos = spendableUtxos;
4798
- if (!utxos || utxos.length === 0) {
4799
- throw new Error("no spendable utxos");
4800
- }
4801
- const sendAmount = toSatoshi(amount);
4802
- const sendCost = toSatoshi(fee);
4803
- const totalNeeded = sendAmount + sendCost;
4804
- const sortedUtxos = utxos.sort((a, b) => b.outputValue - a.outputValue);
4805
- const selectedUtxos = [];
4806
- let accumulatedAmount = 0;
4807
- for (const utxo of sortedUtxos) {
4808
- if (accumulatedAmount >= totalNeeded) break;
4809
- selectedUtxos.push(utxo);
4810
- accumulatedAmount += Number(utxo.outputValue);
4811
- }
4812
- if (accumulatedAmount < totalNeeded) {
4813
- throw new Error("not enough funds to cover amount and fee");
4814
- }
4815
- const utxoDetailPromises = selectedUtxos.map(async (utxo) => {
4816
4986
  try {
4817
- const utxoDetail = await getTxDetail(utxo.txid);
4818
- return { utxo, utxoDetail };
4819
- } catch (error) {
4820
- return { utxo, utxoDetail: null };
4987
+ const serializedTransaction = await this.accountInfo.signTransaction(
4988
+ JSON.stringify({
4989
+ data: preparedTx,
4990
+ types: preparedTxPropsType
4991
+ })
4992
+ );
4993
+ if (serializedTransaction === "") {
4994
+ throw new Error(`eth_signTransaction error data: ${JSON.stringify(txParams)}`);
4995
+ }
4996
+ const data = parseTransaction(serializedTransaction);
4997
+ return data;
4998
+ } catch (err) {
4999
+ throw new Error("eth_signTransaction error" + err);
4821
5000
  }
4822
- });
4823
- const utxoResults = await Promise.all(utxoDetailPromises);
4824
- const inputs = [];
4825
- const usingUtxos = {};
4826
- let addedAmount = 0;
4827
- for (const { utxo, utxoDetail } of utxoResults) {
4828
- if (addedAmount >= totalNeeded) break;
4829
- const { txid, vout, outputValue, address = from } = utxo;
4830
- if (utxoDetail?.hex && !usingUtxos[txid]) {
4831
- inputs.push({
4832
- txId: txid,
4833
- vOut: vout,
4834
- amount: Number(outputValue),
4835
- nonWitnessUtxo: utxoDetail.hex,
4836
- address
5001
+ }
5002
+ async eth_sendRawTransaction([serializedTransaction], rpcUrl) {
5003
+ try {
5004
+ const rpcClient = await this.createPublicClient({ rpcUrl });
5005
+ const hash = await rpcClient.sendRawTransaction({
5006
+ serializedTransaction
4837
5007
  });
4838
- usingUtxos[txid] = 1;
4839
- addedAmount += Number(outputValue);
5008
+ return hash;
5009
+ } catch (error) {
5010
+ console.error("Failed to send transaction via RPC:", error, rpcUrl);
5011
+ throw error;
4840
5012
  }
4841
5013
  }
4842
- if (addedAmount < totalNeeded) {
4843
- throw new Error("not enough funds to cover amount and fee");
4844
- }
4845
- const outputs = [
4846
- {
4847
- address: to,
4848
- amount: sendAmount
5014
+ async getTransaction(hash) {
5015
+ if (!hash || !isHex(hash)) {
5016
+ throw new Error("txId is not valid");
5017
+ }
5018
+ try {
5019
+ const rpcClient = await this.createPublicClient({});
5020
+ const tx = await rpcClient.getTransaction({
5021
+ hash
5022
+ });
5023
+ return tx;
5024
+ } catch (error) {
5025
+ console.error("Failed to send transaction via RPC:", error, hash);
5026
+ throw error;
4849
5027
  }
4850
- ];
4851
- const changeAmount = addedAmount - sendAmount - sendCost;
4852
- if (changeAmount > 0) {
4853
- outputs.push({
4854
- address: from,
4855
- amount: changeAmount
4856
- });
4857
- }
4858
- const params = {
4859
- address: from,
4860
- inputs,
4861
- outputs
4862
- };
4863
- const psbtBase64 = DogecoinUtils.buildPsbtToBase64(params);
4864
- return { psbtBase64, usingUtxos };
4865
- }
4866
- var mydoge = axios.create({
4867
- baseURL: MYDOGE_BASE_URL
4868
- });
4869
- var api = axios.create({
4870
- baseURL: RPC_URL,
4871
- timeout: RPC_TIMEOUT
4872
- });
4873
- async function getBalance(address) {
4874
- if (!address) {
4875
- return {
4876
- address: "",
4877
- balance: 0
4878
- };
4879
5028
  }
4880
- const path = `/address/${address}?page=1&pageSize=10`;
4881
- const api2 = `${MYDOGE_BASE_URL}/wallet/info?route=` + encodeURIComponent(path);
4882
- const res = await mydoge.get(api2);
4883
- return res.data || {};
4884
- }
4885
- async function getTxDetail(txId) {
4886
- if (!txId) {
4887
- return {};
5029
+ };
5030
+ var BaseConfig2 = SupportedChainTypes[ChainTypes.SOL];
5031
+ var TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
5032
+ var MSG_PREFIX = "\xFFsolana offchain";
5033
+
5034
+ // src/solana/utils.ts
5035
+ __toESM(require_bn());
5036
+ new PublicKey(TOKEN_METADATA_PROGRAM_ID);
5037
+ var txToHex = (tx) => {
5038
+ if (tx instanceof VersionedTransaction) {
5039
+ return Buffer.from(tx.serialize()).toString("hex");
4888
5040
  }
4889
- const path = `/tx/${txId}`;
4890
- const api2 = `${MYDOGE_BASE_URL}/wallet/info?route=` + encodeURIComponent(path);
4891
- const res = await mydoge.get(api2);
4892
- return res.data || {};
4893
- }
4894
- var Drc20DetailsCacheKey = "Drc20Details";
4895
- cache.get(Drc20DetailsCacheKey) || {};
4896
- async function getUtxos(address, cursor, result, filter, tx = null) {
4897
- const query = (await mydoge.get(`${MYDOGE_BASE_URL}/utxos/${address}?filter=${filter}${cursor ? `&cursor=${cursor}` : ""}`)).data;
4898
- let { utxos } = query;
4899
- if (tx) {
4900
- utxos = utxos.filter((utxo) => utxo.txid === tx?.txid && utxo.vout === tx?.vout);
5041
+ if (tx instanceof Transaction$1) {
5042
+ return Buffer.from(
5043
+ tx.serialize({
5044
+ requireAllSignatures: false,
5045
+ verifySignatures: false
5046
+ })
5047
+ ).toString("hex");
4901
5048
  }
4902
- result.push(
4903
- ...utxos.map((i) => ({
4904
- txid: i.txid,
4905
- vout: i.vout,
4906
- outputValue: i.satoshis,
4907
- script: i.script_pubkey,
4908
- ...filter === "inscriptions"
4909
- }))
5049
+ return tx;
5050
+ };
5051
+ async function createLegacyTx({ from = "", to = "", amount = 0 }, connection) {
5052
+ const fromPubkey = new PublicKey(from);
5053
+ const toPubkey = new PublicKey(to);
5054
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
5055
+ const transaction = new Transaction$1().add(
5056
+ SystemProgram.transfer({
5057
+ fromPubkey,
5058
+ //payer
5059
+ toPubkey,
5060
+ //toAccount
5061
+ lamports: amount * LAMPORTS_PER_SOL
5062
+ })
4910
5063
  );
4911
- if (result.length && tx) {
4912
- return;
4913
- }
4914
- result = result.sort((a, b) => toBitcoin(b.outputValue) - toBitcoin(a.outputValue));
4915
- if (query.next_cursor) {
4916
- return getUtxos(address, query.next_cursor, result, filter, tx);
4917
- }
4918
- }
4919
- async function getSpendableUtxos(address) {
4920
- const utxos = [];
4921
- await getUtxos(address, 0, utxos, "spendable");
4922
- return utxos;
5064
+ transaction.feePayer = fromPubkey;
5065
+ transaction.recentBlockhash = blockhash;
5066
+ transaction.lastValidBlockHeight = lastValidBlockHeight;
5067
+ return transaction;
4923
5068
  }
4924
- var DuneDetailsCacheKey = "DuneDetails";
4925
- cache.get(DuneDetailsCacheKey) || {};
4926
- var sendDogeTx = async (rawTx) => {
5069
+ async function createTokenLegacyTransaction2({
5070
+ from = "",
5071
+ to = "",
5072
+ amount = 0,
5073
+ tokenAddress = "",
5074
+ priorityFee = 0,
5075
+ // microLamports (1 SOL = 1e6 microLamports)
5076
+ decimals = 6
5077
+ }, connection) {
4927
5078
  try {
4928
- const res = await api.post("/tx/send", { rawTx });
4929
- return res.data;
4930
- } catch (e) {
4931
- if (typeof e?.response?.data === "string") return Promise.reject(e?.response?.data);
4932
- else return Promise.reject(e.message);
5079
+ const fromPubkey = new PublicKey(from);
5080
+ const toPubkey = new PublicKey(to);
5081
+ const tokenPublicKey = new PublicKey(tokenAddress);
5082
+ const fromTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, fromPubkey);
5083
+ const toTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, toPubkey);
5084
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
5085
+ const transaction = new Transaction$1();
5086
+ transaction.feePayer = fromPubkey;
5087
+ transaction.recentBlockhash = blockhash;
5088
+ try {
5089
+ const instruction = createAssociatedTokenAccountInstruction(fromPubkey, toTokenPubKey, toPubkey, tokenPublicKey);
5090
+ transaction.add(instruction);
5091
+ } catch (error) {
5092
+ console.error(error);
5093
+ throw error;
5094
+ }
5095
+ const tokenAmount = new Bignumber(amount).multipliedBy(new Bignumber(10).pow(decimals)).toNumber();
5096
+ transaction.add(
5097
+ createTransferInstruction(fromTokenPubKey, toTokenPubKey, fromPubkey, tokenAmount, [], TOKEN_PROGRAM_ID)
5098
+ );
5099
+ return transaction;
5100
+ } catch (error) {
5101
+ console.error("Error creating transaction:", error);
5102
+ throw error;
4933
5103
  }
4934
- };
4935
-
4936
- // src/dogecoin/service.ts
4937
- var DogecoinService = class _DogecoinService extends BaseService {
5104
+ }
5105
+ var SolanaService = class _SolanaService extends BaseService {
4938
5106
  static instance;
4939
5107
  chainType;
5108
+ rpcUrl;
5109
+ connection;
4940
5110
  constructor(chainType, accountInfo, tomoAppInfo) {
4941
5111
  super(tomoAppInfo, accountInfo);
4942
5112
  this.chainType = chainType;
4943
- }
4944
- //singleton
4945
- static getInstance(chainType, accountInfo, tomoAppInfo) {
4946
- if (!_DogecoinService.instance) {
4947
- _DogecoinService.instance = new _DogecoinService(chainType, accountInfo, tomoAppInfo);
4948
- }
4949
- return _DogecoinService.instance;
4950
- }
4951
- async requestAccounts() {
4952
- const addresses = await this.accountInfo.getCurrent();
4953
- const { publicKey, address } = addresses?.[0] || {};
4954
- if (!address) {
4955
- throw new Error("address is not set");
4956
- }
4957
- const { balance = 0 } = await getBalance(address);
4958
- return {
4959
- address,
4960
- balance,
4961
- approved: true,
4962
- publicKey
4963
- };
4964
- }
4965
- async getAccounts() {
4966
- const accounts = await this.accountInfo.getCurrent();
4967
- return accounts.map((account) => account.address);
4968
- }
4969
- async getConnectionStatus() {
4970
- const addresses = await this.accountInfo.getCurrent();
4971
- const { address } = addresses?.[0] || {};
4972
- if (!address) {
4973
- throw new Error("address is not set");
4974
- }
4975
- return {
4976
- connected: true,
4977
- address,
4978
- selectedWalletAddress: address
5113
+ const config = {
5114
+ commitment: "confirmed",
5115
+ disableRetryOnRateLimit: false,
5116
+ confirmTransactionInitialTimeout: 12e4,
5117
+ fetch: (url, options) => {
5118
+ return fetch(url, { ...options });
5119
+ }
4979
5120
  };
5121
+ const domain = TomoApiDomains[tomoAppInfo.tomoStage];
5122
+ const rpcUrl = `${domain}/rpc/v1/solana`;
5123
+ this.rpcUrl = rpcUrl;
5124
+ this.connection = new Connection(rpcUrl, config);
4980
5125
  }
4981
- async getBalance() {
4982
- const addresses = await this.accountInfo.getCurrent();
4983
- const { address } = addresses?.[0] || {};
4984
- if (!address) {
4985
- throw new Error("address is not set");
5126
+ static getInstance(chainType, accountInfo, tomoAppInfo) {
5127
+ if (!_SolanaService.instance) {
5128
+ _SolanaService.instance = new _SolanaService(chainType, accountInfo, tomoAppInfo);
4986
5129
  }
4987
- const { balance = 0 } = await getBalance(address);
4988
- return {
4989
- address,
4990
- balance
4991
- };
5130
+ return _SolanaService.instance;
4992
5131
  }
4993
- async signMessage({ message, type }) {
4994
- if (type) {
4995
- throw new Error("bip322simple not support.");
4996
- }
5132
+ async getCurrentWalletAccount() {
5133
+ return await this.accountInfo.getCurrent();
5134
+ }
5135
+ async getAccount() {
5136
+ const { address: currentAddress, publicKey } = await this.accountInfo.getCurrent();
5137
+ return { publicKey: publicKey || currentAddress, address: currentAddress };
5138
+ }
5139
+ async signMessage(params) {
5140
+ const { address: currentAddress, publicKey } = await this.accountInfo.getCurrent();
5141
+ const { message } = params;
4997
5142
  const signature = await this.accountInfo.signMessage(message);
4998
5143
  return {
4999
- signedMessage: signature
5144
+ message: MSG_PREFIX + message,
5145
+ signature,
5146
+ publicKey: publicKey || currentAddress
5000
5147
  };
5001
5148
  }
5002
- async requestSignedMessage({
5003
- message,
5004
- type
5005
- }) {
5006
- return await this.signMessage({
5007
- message,
5008
- type
5009
- });
5149
+ async signIn(params) {
5150
+ const { address: currentAddress, publicKey } = await this.accountInfo.getCurrent();
5151
+ const { signature = "" } = await this.signMessage(params);
5152
+ return {
5153
+ address: currentAddress,
5154
+ publicKey: publicKey || currentAddress,
5155
+ signature,
5156
+ signedMessage: MSG_PREFIX + params.message
5157
+ };
5010
5158
  }
5011
- async requestDecryptedMessage({ message }) {
5012
- try {
5013
- if (!this.accountInfo?.decryptedMessage) {
5014
- throw new Error("decryptedMessage is not supported");
5015
- }
5016
- const unsignRes = await this.accountInfo?.decryptedMessage(message);
5017
- const { text = "" } = JSON.parse(unsignRes);
5018
- return {
5019
- decryptedMessage: text
5020
- };
5021
- } catch (err) {
5022
- throw new Error("requestDecryptedMessage err:" + JSON.stringify(err));
5159
+ async request({ method, params }) {
5160
+ if (!this[method]) {
5161
+ throw new Error(`${method} in request is not supported`);
5023
5162
  }
5163
+ return this[method](params);
5024
5164
  }
5025
5165
  async _queryGasInfo(txData) {
5026
- const { chainId, amount = 0, decimals = 8 } = txData;
5166
+ const { tokenAddress, chainId, amount = 0, decimals = 9 } = txData;
5167
+ const value = tokenAddress ? "0x1" : toHex$1(parseUnits(amount.toString(), decimals));
5168
+ let callData = "0x";
5169
+ if (tokenAddress) {
5170
+ const transaction = await createTokenLegacyTransaction2(txData, this.connection);
5171
+ callData = "0x" + txToHex(transaction);
5172
+ } else {
5173
+ const transaction = await createLegacyTx(txData, this.connection);
5174
+ callData = "0x" + txToHex(transaction);
5175
+ }
5027
5176
  const gasLimitParam = {
5028
5177
  from: txData.from,
5029
5178
  to: txData.to,
5030
- value: toHex(parseUnits(amount.toString(), decimals))
5179
+ value
5031
5180
  };
5032
5181
  const queryGasParams = {
5033
- chainIndex: Number(chainId || BaseConfig2.chainId),
5034
- callData: "0x",
5182
+ chainIndex: Number(chainId),
5183
+ callData,
5035
5184
  gasLimitParam,
5036
5185
  addressList: [txData.from]
5037
5186
  };
@@ -5044,85 +5193,149 @@ var DogecoinService = class _DogecoinService extends BaseService {
5044
5193
  params: queryGasParams
5045
5194
  });
5046
5195
  if (!success) {
5047
- console.error("queryGasInfo doge", success, txData, queryGasParams, gasInfo);
5196
+ console.error("queryGasInfo solana", txData, message, gasInfo);
5048
5197
  const { gasFee } = BaseConfig2;
5049
5198
  return {
5050
5199
  success: true,
5051
5200
  gasFee: gasFee.toString()
5052
5201
  };
5053
5202
  }
5054
- const { baseFee = 1, gasLimit = 1 } = gasInfo;
5203
+ const nativeChainId = SupportedChainTypes[this.chainType].chainId;
5204
+ const { nativeCurrency } = await this.networks.getNetworkByChainId(nativeChainId);
5205
+ const { baseFee = 1, gasLimit = 0 } = gasInfo;
5206
+ const baseFeeBN = new BigNumber(baseFee).times(2);
5207
+ const gasLimitBN = new BigNumber(gasLimit);
5055
5208
  const calcFee = (feeLevel) => {
5056
- const gasFee = new BigNumber(gasInfo[feeLevel] || baseFee).times(gasLimit);
5057
- return toBitcoin(gasFee.toNumber());
5058
- };
5059
- const fees = {
5060
- low: calcFee("priorityFeeLow"),
5061
- medium: calcFee("priorityFeeMedium"),
5062
- high: calcFee("priorityFeeHigh")
5209
+ const priorityFee = gasLimitBN.times(gasInfo[feeLevel] || 0).dividedBy(1e6).integerValue(BigNumber.ROUND_DOWN);
5210
+ const gasFee = baseFeeBN.plus(priorityFee);
5211
+ return formatUnits(BigInt(gasFee.toNumber()), nativeCurrency?.decimals || 9);
5063
5212
  };
5064
5213
  return {
5065
5214
  success: true,
5066
- fees,
5067
- gasFee: fees.high
5215
+ fees: {
5216
+ low: calcFee("priorityFeeLow"),
5217
+ medium: calcFee("priorityFeeMedium"),
5218
+ high: calcFee("priorityFeeHigh")
5219
+ },
5220
+ gasFee: calcFee("priorityFeeHigh")
5068
5221
  };
5069
5222
  }
5070
- async requestPsbt({
5071
- rawTx,
5072
- signOnly
5073
- }) {
5074
- const psbtBase64 = await DogecoinUtils.rawTxToPsbtBase64(rawTx);
5075
- const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify({ psbtBase64 }));
5076
- if (!signedPsbt) {
5077
- return null;
5223
+ async sendSignedTx(signedTx) {
5224
+ let rawTx;
5225
+ try {
5226
+ const hexBuffer = Buffer.from(signedTx, "hex");
5227
+ VersionedTransaction.deserialize(hexBuffer);
5228
+ rawTx = hexBuffer;
5229
+ } catch (error) {
5230
+ const base58Buffer = Buffer.from(toBase58());
5231
+ VersionedTransaction.deserialize(base58Buffer);
5232
+ rawTx = base58Buffer;
5078
5233
  }
5079
- const signedRawTx = DogecoinUtils.extractPsbtTransaction(signedPsbt, 1e5);
5080
- if (signOnly) {
5081
- return {
5082
- signedRawTx
5083
- };
5234
+ const transaction = VersionedTransaction.deserialize(rawTx);
5235
+ if (!transaction.signatures || transaction.signatures.length === 0) {
5236
+ throw new Error("lack of sign");
5084
5237
  }
5085
- const { txid = "" } = await sendDogeTx(signedRawTx);
5086
- return {
5087
- txId: txid,
5088
- signedRawTx
5238
+ const confirmationStrategy = {
5239
+ commitment: "confirmed",
5240
+ preflightCommitment: "processed",
5241
+ skipPreflight: false,
5242
+ maxRetries: 5
5089
5243
  };
5090
- }
5091
- async requestTransaction(txData) {
5092
- const spendableUtxos = txData?.spendableUtxos;
5093
- if (txData.amountMismatch) {
5094
- throw new Error("balance_insufficient");
5244
+ try {
5245
+ const simulation = await this.connection.simulateTransaction(transaction);
5246
+ if (simulation.value.err) {
5247
+ const logs = simulation.value.logs || [];
5248
+ const message = logs[logs.length - 1] || simulation.value.err || "Unknown error occurred";
5249
+ console.error("send err logs:", logs, message);
5250
+ throw new Error(`Transaction failed: ${message}`);
5251
+ }
5252
+ const signature = await sendAndConfirmRawTransaction(this.connection, rawTx, confirmationStrategy);
5253
+ return signature;
5254
+ } catch (error) {
5255
+ if (error.message.includes("TransactionExpiredTimeoutError")) {
5256
+ const status = await this.connection.getSignatureStatus(transaction.signatures[0].toString());
5257
+ if (status.value?.confirmationStatus === "confirmed") {
5258
+ return transaction.signatures[0].toString();
5259
+ }
5260
+ }
5261
+ throw error;
5095
5262
  }
5096
- if (!spendableUtxos || spendableUtxos?.length === 0) {
5097
- txData.spendableUtxos = await getSpendableUtxos(txData.from);
5263
+ }
5264
+ async signTransaction({ rawTransaction }) {
5265
+ const params = {
5266
+ rawTransaction,
5267
+ walletId: -1,
5268
+ rpcUrl: this.rpcUrl
5269
+ };
5270
+ const signature = await this.accountInfo.signTransaction(JSON.stringify(params));
5271
+ return signature;
5272
+ }
5273
+ async signAndSendTransaction({ rawTransaction }) {
5274
+ const signedTx = await this.signTransaction({ rawTransaction });
5275
+ if (!signedTx) {
5276
+ return "";
5098
5277
  }
5099
- const { psbtBase64, usingUtxos } = await createPsbt(txData);
5100
- const signedPsbt = await this.accountInfo.signTransaction(JSON.stringify({ psbtBase64 }));
5101
- const signedTx = DogecoinUtils.extractPsbtTransaction(signedPsbt, 1e5);
5102
- if (signedTx === "") {
5103
- throw new Error("Error: sign transaction err.");
5278
+ const signature = await this.sendSignedTx(signedTx);
5279
+ return signature;
5280
+ }
5281
+ async signAllTransactions({ rawTransactions }) {
5282
+ throw new Error("no support");
5283
+ }
5284
+ async signAndSendAllTransactions({ rawTransactions }) {
5285
+ throw new Error("no support");
5286
+ }
5287
+ async queryRent(params) {
5288
+ const MIN_ACCOUNT_ACTIVATION_SOL = "0.0009";
5289
+ const SPL_TOKEN_ACCOUNT_CREATION_FEE = "0.001";
5290
+ try {
5291
+ const { toAddress, tokenAddress } = params;
5292
+ let minTransferAmount = "0";
5293
+ let createSplTokenFee = null;
5294
+ if (tokenAddress) {
5295
+ const tokenAccountExists = await this.checkTokenAccount(toAddress, tokenAddress);
5296
+ if (!tokenAccountExists) {
5297
+ createSplTokenFee = SPL_TOKEN_ACCOUNT_CREATION_FEE;
5298
+ }
5299
+ } else {
5300
+ const accountExists = await this.checkAccountExists(toAddress);
5301
+ if (!accountExists) {
5302
+ minTransferAmount = MIN_ACCOUNT_ACTIVATION_SOL;
5303
+ }
5304
+ }
5305
+ const rentInfo = {
5306
+ minTransferAmount,
5307
+ createSplTokenFee
5308
+ };
5309
+ return rentInfo;
5310
+ } catch (error) {
5311
+ console.error("queryRent error:", error);
5312
+ const defaultRentInfo = {
5313
+ minTransferAmount: "0",
5314
+ createSplTokenFee: null
5315
+ };
5316
+ return defaultRentInfo;
5104
5317
  }
5318
+ }
5319
+ async checkAccountExists(address) {
5105
5320
  try {
5106
- const { txid } = await sendDogeTx(signedTx);
5107
- addUsedUtxos(usingUtxos);
5108
- return { txId: txid };
5109
- } catch (err) {
5110
- throw new Error("Error: send transaction err." + JSON.stringify(err));
5321
+ const accountInfo = await this.connection.getAccountInfo(new PublicKey(address));
5322
+ return accountInfo !== null;
5323
+ } catch (error) {
5324
+ console.warn("Failed to check account exists:", error);
5325
+ return false;
5111
5326
  }
5112
5327
  }
5113
- async getTransactionStatus({ txId }) {
5114
- const transaction = await getTxDetail(txId);
5115
- if (!transaction?.txid) {
5116
- return null;
5328
+ async checkTokenAccount(ownerAddress, tokenMint) {
5329
+ try {
5330
+ const owner = new PublicKey(ownerAddress);
5331
+ const mint = new PublicKey(tokenMint);
5332
+ const associatedTokenAddress = await getAssociatedTokenAddress(mint, owner);
5333
+ const accountInfo = await this.connection.getAccountInfo(associatedTokenAddress);
5334
+ return accountInfo !== null;
5335
+ } catch (error) {
5336
+ console.warn("Failed to check token account:", error);
5337
+ return false;
5117
5338
  }
5118
- return {
5119
- txId: transaction.txid,
5120
- confirmations: transaction.confirmations,
5121
- status: transaction.confirmations > 0 ? "confirmed" : "pending",
5122
- dogeAmount: transaction.vout[0]?.value,
5123
- blockTime: transaction.blockTime,
5124
- address: transaction.vout[0]?.addresses[0]
5125
- };
5126
5339
  }
5127
5340
  };
5128
5341
  var TomoWallet = class _TomoWallet extends BaseService {
@@ -5216,4 +5429,4 @@ var ChainTypeServices = {
5216
5429
  [ChainTypes.DOGE]: DogecoinService
5217
5430
  };
5218
5431
 
5219
- export { AccountType, ChainTypeServices, DogecoinService, EvmService, SolanaService, TomoWallet, TxTypes };
5432
+ export { AccountType, ChainTypeServices, rpc_exports as DogecoinAPI, DogecoinAddress, DogecoinService, DogecoinUtils, EvmService, SolanaService, TomoWallet, TransactionParser, TxTypes, addUsedUtxos, createPsbt, decodePsbt, getUsedUtxos, toBitcoin, toSatoshi };