diff --git a/packages/extension/package.json b/packages/extension/package.json index ffefa2ffa..982cd59ae 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,6 +1,6 @@ { "name": "@enkryptcom/extension", - "version": "1.27.0", + "version": "1.28.0", "private": true, "scripts": { "zip": "cd dist; zip -r release.zip *;", diff --git a/packages/extension/src/libs/keyring/public-keyring.ts b/packages/extension/src/libs/keyring/public-keyring.ts index 3ad208cec..87fbc4349 100644 --- a/packages/extension/src/libs/keyring/public-keyring.ts +++ b/packages/extension/src/libs/keyring/public-keyring.ts @@ -64,6 +64,16 @@ class PublicKeyRing { walletType: WalletType.mnemonic, isHardware: false, }; + allKeys["ltc1qccf4af6j3xm9v3r6ujt7dlmvazywzq82hnuwgx"] = { + address: "ltc1qccf4af6j3xm9v3r6ujt7dlmvazywzq82hnuwgx", + basePath: "m/49'/2'/0'/1", + name: "fake ltc account #4", + pathIndex: 0, + publicKey: "0x0", + signerType: SignerType.secp256k1btc, + walletType: WalletType.mnemonic, + isHardware: false, + }; } return allKeys; } diff --git a/packages/extension/src/providers/bitcoin/libs/activity-handlers/index.ts b/packages/extension/src/providers/bitcoin/libs/activity-handlers/index.ts index 03e77198c..dc0e381ea 100644 --- a/packages/extension/src/providers/bitcoin/libs/activity-handlers/index.ts +++ b/packages/extension/src/providers/bitcoin/libs/activity-handlers/index.ts @@ -1,3 +1,3 @@ import haskoinHandler from "./providers/haskoin"; - -export { haskoinHandler }; +import ssHandler from "./providers/ss"; +export { haskoinHandler, ssHandler }; diff --git a/packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/haskoin/index.ts b/packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/haskoin/index.ts index 7dd9c5f51..5d4837d30 100644 --- a/packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/haskoin/index.ts +++ b/packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/haskoin/index.ts @@ -43,6 +43,9 @@ export default async ( if (relevantOut) { toAddress = relevantOut.address; value = relevantOut.value; + } else { + toAddress = tx.outputs[0].address; + value = Number(tx.outputs[0].value); } } diff --git a/packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/ss/index.ts b/packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/ss/index.ts new file mode 100644 index 000000000..1a0af2958 --- /dev/null +++ b/packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/ss/index.ts @@ -0,0 +1,108 @@ +import MarketData from "@/libs/market-data"; +import { SSTxType } from "@/providers/bitcoin/types"; +import { + Activity, + ActivityStatus, + ActivityType, + BTCRawInfo, +} from "@/types/activity"; +import { BaseNetwork } from "@/types/base-network"; +export default async ( + network: BaseNetwork, + pubkey: string +): Promise => { + return fetch( + `${network.node}/api/v1/account/${network.displayAddress( + pubkey + )}/txs?pageSize=40` + ) + .then((res) => res.json()) + .then(async (txs: { txs: SSTxType[] }) => { + if ((txs as any).message) return []; + let tokenPrice = "0"; + if (network.coingeckoID) { + const marketData = new MarketData(); + await marketData + .getTokenPrice(network.coingeckoID) + .then((mdata) => (tokenPrice = mdata || "0")); + } + + const address = network.displayAddress(pubkey); + const cleanedTxs = txs.txs.map((tx) => { + return { + ...tx, + vin: tx.vin.filter((vi) => vi.addresses), + vout: tx.vout.filter((vo) => vo.addresses), + }; + }); + return cleanedTxs.map((tx) => { + const isIncoming = !tx.vin.find((i) => i.addresses![0] === address); + console.log(isIncoming, tx.vin, tx.vout, address); + let toAddress = ""; + let value = 0; + + if (isIncoming) { + const relevantOut = tx.vout.find( + (tx) => tx.addresses![0] === address + ); + if (relevantOut) { + toAddress = relevantOut.addresses![0]; + value = Number(relevantOut.value); + } + } else { + const relevantOut = tx.vout.find( + (tx) => tx.addresses![0] !== address + ); + if (relevantOut) { + toAddress = relevantOut.addresses![0]; + value = Number(relevantOut.value); + } else { + toAddress = tx.vout[0].addresses![0]; + value = Number(tx.vout[0].value); + } + } + + const rawInfo: BTCRawInfo = { + blockNumber: tx.blockHeight!, + fee: Number(tx.fee), + inputs: tx.vin.map((input) => ({ + address: input.addresses![0], + value: Number(input.value), + })), + outputs: tx.vout.map((output) => ({ + address: output.addresses![0], + value: Number(output.value), + })), + transactionHash: tx.txid, + timestamp: tx.timestamp * 1000, + }; + const act: Activity = { + from: tx.vin[0].addresses![0], + isIncoming, + network: network.name, + status: + tx.blockHeight > 0 + ? ActivityStatus.success + : ActivityStatus.pending, + timestamp: tx.timestamp * 1000, + to: toAddress, + token: { + decimals: network.decimals, + icon: network.icon, + name: network.name_long, + symbol: network.currencyName, + coingeckoID: network.coingeckoID, + price: tokenPrice, + }, + transactionHash: tx.txid, + type: ActivityType.transaction, + value: value.toString(), + rawInfo: rawInfo, + }; + return act; + }); + }) + .catch(() => { + return []; + }); +}; diff --git a/packages/extension/src/providers/bitcoin/libs/api-ss.ts b/packages/extension/src/providers/bitcoin/libs/api-ss.ts new file mode 100644 index 000000000..42671d7c5 --- /dev/null +++ b/packages/extension/src/providers/bitcoin/libs/api-ss.ts @@ -0,0 +1,123 @@ +import { BTCRawInfo } from "@/types/activity"; +import { ProviderAPIInterface } from "@/types/provider"; +import { + BitcoinNetworkInfo, + HaskoinUnspentType, + SSTxType, + SSUnspentType, +} from "../types"; +import { toBN } from "web3-utils"; +import cacheFetch from "@/libs/cache-fetch"; +import { getAddress as getBitcoinAddress } from "../types/bitcoin-network"; + +class API implements ProviderAPIInterface { + node: string; + networkInfo: BitcoinNetworkInfo; + + constructor(node: string, networkInfo: BitcoinNetworkInfo) { + this.node = node; + this.networkInfo = networkInfo; + } + + public get api() { + return this; + } + private getAddress(pubkey: string) { + return getBitcoinAddress(pubkey, this.networkInfo); + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + async init(): Promise {} + async getTransactionStatus(hash: string): Promise { + return fetch(`${this.node}/api/v1/tx/${hash}`) + .then((res) => res.json()) + .then((tx: SSTxType) => { + if ((tx as any).message) return null; + if (tx.blockHeight < 0) return null; + const rawInfo: BTCRawInfo = { + blockNumber: tx.blockHeight, + fee: Number(tx.fee), + inputs: tx.vin + .filter((t) => t.addresses && t.addresses.length) + .map((input) => ({ + address: input.addresses![0], + value: Number(input.value), + })), + outputs: tx.vout + .filter((t) => t.addresses && t.addresses.length) + .map((output) => ({ + address: output.addresses![0], + value: Number(output.value), + })), + transactionHash: tx.txid, + timestamp: tx.timestamp * 1000, + }; + return rawInfo; + }); + } + async getBalance(pubkey: string): Promise { + const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); + return fetch(`${this.node}/api/v1/account/${address}`) + .then((res) => res.json()) + .then((balance: { balance: string; unconfirmedBalance: string }) => { + if ((balance as any).message) return "0"; + return toBN(balance.balance) + .add(toBN(balance.unconfirmedBalance)) + .toString(); + }); + } + async broadcastTx(rawtx: string): Promise { + return fetch(`${this.node}/api/v1/send`, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ hex: rawtx }), + }) + .then((res) => res.json()) + .then((response) => { + if (response.error) { + return Promise.reject(response.message); + } + return true; + }); + } + async SSToHaskoinUTXOs( + SSUTXOs: SSUnspentType[], + address: string + ): Promise { + const ret: HaskoinUnspentType[] = []; + for (const utx of SSUTXOs) { + const res = (await cacheFetch({ + url: `${this.node}/api/v1/tx/${utx.txid}`, + })) as SSTxType; + ret.push({ + address, + block: { + height: utx.height, + position: 0, + }, + index: utx.vout, + pkscript: res.vout[utx.vout].scriptPubKey.hex, + txid: utx.txid, + value: Number(utx.value), + raw: res.hex, + }); + } + ret.sort((a, b) => { + return a.value - b.value; + }); + return ret; + } + + async getUTXOs(pubkey: string): Promise { + const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); + return fetch(`${this.node}/api/v1/account/${address}/utxos`) + .then((res) => res.json()) + .then((utxos: SSUnspentType[]) => { + if ((utxos as any).message || !utxos.length) return []; + return this.SSToHaskoinUTXOs(utxos, address); + }); + } +} +export default API; diff --git a/packages/extension/src/providers/bitcoin/libs/api.ts b/packages/extension/src/providers/bitcoin/libs/api.ts index 924f37df1..1ee289e6f 100644 --- a/packages/extension/src/providers/bitcoin/libs/api.ts +++ b/packages/extension/src/providers/bitcoin/libs/api.ts @@ -1,14 +1,13 @@ import { BTCRawInfo } from "@/types/activity"; import { ProviderAPIInterface } from "@/types/provider"; -import { hexToBuffer } from "@enkryptcom/utils"; import { BitcoinNetworkInfo, HaskoinBalanceType, HaskoinTxType, HaskoinUnspentType, } from "../types"; -import { payments } from "bitcoinjs-lib"; import { toBN } from "web3-utils"; +import { getAddress as getBitcoinAddress } from "../types/bitcoin-network"; class API implements ProviderAPIInterface { node: string; @@ -23,11 +22,7 @@ class API implements ProviderAPIInterface { return this; } private getAddress(pubkey: string) { - const { address } = payments.p2wpkh({ - pubkey: hexToBuffer(pubkey), - network: this.networkInfo, - }); - return address as string; + return getBitcoinAddress(pubkey, this.networkInfo); } // eslint-disable-next-line @typescript-eslint/no-empty-function async init(): Promise {} diff --git a/packages/extension/src/providers/bitcoin/libs/ss-fee-handler.ts b/packages/extension/src/providers/bitcoin/libs/ss-fee-handler.ts new file mode 100644 index 000000000..1cc641602 --- /dev/null +++ b/packages/extension/src/providers/bitcoin/libs/ss-fee-handler.ts @@ -0,0 +1,33 @@ +import { GasPriceTypes } from "@/providers/common/types"; + +interface FeeType { + fast: { + satsPerKiloByte: number; + }; + average: { + satsPerKiloByte: number; + }; + slow: { + satsPerKiloByte: number; + }; +} +const SSFeeHandler = async ( + url: string +): Promise> => { + return fetch(url) + .then((res) => res.json()) + .then((json: FeeType) => { + if (json.fast.satsPerKiloByte < 0) + json.fast.satsPerKiloByte = json.average.satsPerKiloByte; + return { + [GasPriceTypes.FASTEST]: + Math.ceil(json.fast.satsPerKiloByte / 1024) + 5, + [GasPriceTypes.FAST]: Math.ceil(json.fast.satsPerKiloByte / 1024) + 3, + [GasPriceTypes.REGULAR]: + Math.ceil(json.average.satsPerKiloByte / 1024) + 2, + [GasPriceTypes.ECONOMY]: Math.ceil(json.slow.satsPerKiloByte / 1024), + }; + }); +}; + +export default SSFeeHandler; diff --git a/packages/extension/src/providers/bitcoin/libs/utils.ts b/packages/extension/src/providers/bitcoin/libs/utils.ts index 0c72aee8b..42f61c600 100644 --- a/packages/extension/src/providers/bitcoin/libs/utils.ts +++ b/packages/extension/src/providers/bitcoin/libs/utils.ts @@ -1,9 +1,10 @@ -import { BitcoinNetworkInfo } from "../types"; +import { BitcoinNetworkInfo, HaskoinUnspentType } from "../types"; import { address as BTCAddress } from "bitcoinjs-lib"; import { GasPriceTypes } from "@/providers/common/types"; import { fromBase } from "@enkryptcom/utils"; import BigNumber from "bignumber.js"; import { BitcoinNetwork } from "../types/bitcoin-network"; +import { BTCTxInfo } from "../ui/types"; const isAddress = (address: string, network: BitcoinNetworkInfo): boolean => { try { @@ -13,6 +14,26 @@ const isAddress = (address: string, network: BitcoinNetworkInfo): boolean => { return false; } }; + +const getTxInfo = (utxos: HaskoinUnspentType[]): BTCTxInfo => { + const txInfo: BTCTxInfo = { + inputs: [], + outputs: [], + }; + utxos.forEach((u) => { + txInfo.inputs.push({ + hash: u.txid, + index: u.index, + raw: u.raw, + witnessUtxo: { + script: u.pkscript, + value: u.value, + }, + }); + }); + return txInfo; +}; + const getGasCostValues = async ( network: BitcoinNetwork, byteSize: number, @@ -66,4 +87,4 @@ const getGasCostValues = async ( }; return gasCostValues; }; -export { isAddress, getGasCostValues }; +export { isAddress, getGasCostValues, getTxInfo }; diff --git a/packages/extension/src/providers/bitcoin/networks/bitcoin-testnet.ts b/packages/extension/src/providers/bitcoin/networks/bitcoin-testnet.ts index 1093e9357..fe2c96906 100644 --- a/packages/extension/src/providers/bitcoin/networks/bitcoin-testnet.ts +++ b/packages/extension/src/providers/bitcoin/networks/bitcoin-testnet.ts @@ -2,10 +2,12 @@ import { NetworkNames } from "@enkryptcom/types"; import { BitcoinNetwork, BitcoinNetworkOptions, + PaymentType, } from "../types/bitcoin-network"; import { haskoinHandler } from "../libs/activity-handlers"; import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; import { GasPriceTypes } from "@/providers/common/types"; +import HaskoinAPI from "../libs/api"; const bitcoinOptions: BitcoinNetworkOptions = { name: NetworkNames.BitcoinTest, @@ -19,10 +21,12 @@ const bitcoinOptions: BitcoinNetworkOptions = { currencyNameLong: "Test Bitcoin", icon: require("./icons/tbtc.svg"), decimals: 8, - node: "https://api.blockchain.info/haskoin-store/btc-testnet/", + dust: 0.00000546, + node: "https://partners.mewapi.io/nodes/hk/btct/", activityHandler: wrapActivityHandler(haskoinHandler), basePath: "m/49'/1'/0'/0", coingeckoID: "bitcoin", + apiType: HaskoinAPI, feeHandler: () => Promise.resolve({ [GasPriceTypes.FASTEST]: 25, @@ -41,6 +45,7 @@ const bitcoinOptions: BitcoinNetworkOptions = { scriptHash: 0xc4, wif: 0xef, dustThreshold: null, + paymentType: PaymentType.P2WPKH, }, }; diff --git a/packages/extension/src/providers/bitcoin/networks/bitcoin.ts b/packages/extension/src/providers/bitcoin/networks/bitcoin.ts index d895acae0..8e17d7db0 100644 --- a/packages/extension/src/providers/bitcoin/networks/bitcoin.ts +++ b/packages/extension/src/providers/bitcoin/networks/bitcoin.ts @@ -2,10 +2,12 @@ import { NetworkNames } from "@enkryptcom/types"; import { BitcoinNetwork, BitcoinNetworkOptions, + PaymentType, } from "../types/bitcoin-network"; import { haskoinHandler } from "../libs/activity-handlers"; import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; import BTCFeeHandler from "../libs/btc-fee-handler"; +import HaskoinAPI from "../libs/api"; const bitcoinOptions: BitcoinNetworkOptions = { name: NetworkNames.Bitcoin, @@ -18,11 +20,13 @@ const bitcoinOptions: BitcoinNetworkOptions = { currencyNameLong: "Bitcoin", icon: require("./icons/btc.svg"), decimals: 8, - node: "https://api.blockchain.info/haskoin-store/btc/", + node: "https://partners.mewapi.io/nodes/hk/btc/", coingeckoID: "bitcoin", activityHandler: wrapActivityHandler(haskoinHandler), basePath: "m/49'/0'/0'/0", feeHandler: BTCFeeHandler, + apiType: HaskoinAPI, + dust: 0.00000546, networkInfo: { messagePrefix: "\x18Bitcoin Signed Message:\n", bech32: "bc", @@ -34,6 +38,7 @@ const bitcoinOptions: BitcoinNetworkOptions = { scriptHash: 0x05, wif: 0x80, dustThreshold: null, + paymentType: PaymentType.P2WPKH, }, }; diff --git a/packages/extension/src/providers/bitcoin/networks/dogecoin.ts b/packages/extension/src/providers/bitcoin/networks/dogecoin.ts new file mode 100644 index 000000000..63b06beea --- /dev/null +++ b/packages/extension/src/providers/bitcoin/networks/dogecoin.ts @@ -0,0 +1,49 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { + BitcoinNetwork, + BitcoinNetworkOptions, + PaymentType, +} from "../types/bitcoin-network"; +import { ssHandler } from "../libs/activity-handlers"; +import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import SSFeeHandler from "../libs/ss-fee-handler"; +import SSApi from "../libs/api-ss"; + +const dogeOptions: BitcoinNetworkOptions = { + name: NetworkNames.Dogecoin, + name_long: "Dogecoin", + homePage: "https://dogecoin.com/", + blockExplorerTX: "https://dogechain.info/tx/[[txHash]]", + blockExplorerAddr: "https://dogechain.info/address/[[address]]", + isTestNetwork: false, + currencyName: "Doge", + currencyNameLong: "Dogecoin", + icon: require("./icons/doge.svg"), + decimals: 8, + node: "https://partners.mewapi.io/nodes/ss/doge", + coingeckoID: "dogecoin", + apiType: SSApi, + dust: 0.01, + activityHandler: wrapActivityHandler(ssHandler), + basePath: "m/44'/3'/0'/0", + feeHandler: () => { + return SSFeeHandler("https://partners.mewapi.io/nodes/ss/doge/api/v1/fees"); + }, + networkInfo: { + messagePrefix: "\x19Dogecoin Signed Message:\n", + bech32: "dc", + bip32: { + public: 0x02facafd, + private: 0x02fac398, + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e, + dustThreshold: null, + paymentType: PaymentType.P2PKH, + }, +}; + +const dogecoin = new BitcoinNetwork(dogeOptions); + +export default dogecoin; diff --git a/packages/extension/src/providers/bitcoin/networks/icons/doge.svg b/packages/extension/src/providers/bitcoin/networks/icons/doge.svg new file mode 100644 index 000000000..c435731dc --- /dev/null +++ b/packages/extension/src/providers/bitcoin/networks/icons/doge.svg @@ -0,0 +1 @@ +Dogecoin (DOGE) \ No newline at end of file diff --git a/packages/extension/src/providers/bitcoin/networks/icons/ltc.svg b/packages/extension/src/providers/bitcoin/networks/icons/ltc.svg new file mode 100644 index 000000000..13e76a40e --- /dev/null +++ b/packages/extension/src/providers/bitcoin/networks/icons/ltc.svg @@ -0,0 +1 @@ +litecoin-ltc-logo \ No newline at end of file diff --git a/packages/extension/src/providers/bitcoin/networks/index.ts b/packages/extension/src/providers/bitcoin/networks/index.ts index 9d0dee744..2cf52f449 100644 --- a/packages/extension/src/providers/bitcoin/networks/index.ts +++ b/packages/extension/src/providers/bitcoin/networks/index.ts @@ -1,7 +1,11 @@ import btcNode from "./bitcoin"; import btcTestNode from "./bitcoin-testnet"; +import ltcNode from "./litecoin"; +import dogeNode from "./dogecoin"; export default { bitcoin: btcNode, bitcoinTest: btcTestNode, + litecoin: ltcNode, + dogecoin: dogeNode, }; diff --git a/packages/extension/src/providers/bitcoin/networks/litecoin.ts b/packages/extension/src/providers/bitcoin/networks/litecoin.ts new file mode 100644 index 000000000..a565d52e9 --- /dev/null +++ b/packages/extension/src/providers/bitcoin/networks/litecoin.ts @@ -0,0 +1,49 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { + BitcoinNetwork, + BitcoinNetworkOptions, + PaymentType, +} from "../types/bitcoin-network"; +import { ssHandler } from "../libs/activity-handlers"; +import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import SSFeeHandler from "../libs/ss-fee-handler"; +import SSApi from "../libs/api-ss"; + +const litecoinOptions: BitcoinNetworkOptions = { + name: NetworkNames.Litecoin, + name_long: "Litecoin", + homePage: "https://litecoin.org/", + blockExplorerTX: "https://explorer.btc.com/ltc/transaction/[[txHash]]", + blockExplorerAddr: "https://explorer.btc.com/ltc/address/[[address]]", + isTestNetwork: false, + currencyName: "LTC", + currencyNameLong: "Litecoin", + icon: require("./icons/ltc.svg"), + decimals: 8, + node: "https://partners.mewapi.io/nodes/ss/ltc", + coingeckoID: "litecoin", + dust: 0.0001, + apiType: SSApi, + activityHandler: wrapActivityHandler(ssHandler), + basePath: "m/49'/2'/0'/0", + feeHandler: () => { + return SSFeeHandler("https://partners.mewapi.io/nodes/ss/ltc/api/v1/fees"); + }, + networkInfo: { + messagePrefix: "\x19Litecoin Signed Message:\n", + bech32: "ltc", + bip32: { + public: 0x019da462, + private: 0x019d9cfe, + }, + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0, + dustThreshold: null, + paymentType: PaymentType.P2WPKH, + }, +}; + +const litecoin = new BitcoinNetwork(litecoinOptions); + +export default litecoin; diff --git a/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts b/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts index 7867c650c..3258c4866 100644 --- a/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts +++ b/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts @@ -19,7 +19,13 @@ import { CoinGeckoTokenMarket } from "@/libs/market-data/types"; import Sparkline from "@/libs/sparkline"; import { BTCToken } from "./btc-token"; import { GasPriceTypes } from "@/providers/common/types"; +import type HaskoinAPI from "@/providers/bitcoin/libs/api"; +import type SSAPI from "@/providers/bitcoin/libs/api-ss"; +export enum PaymentType { + P2PKH = "p2pkh", + P2WPKH = "p2wpkh", +} export interface BitcoinNetworkOptions { name: NetworkNames; name_long: string; @@ -35,16 +41,27 @@ export interface BitcoinNetworkOptions { coingeckoID?: string; basePath: string; networkInfo: BitcoinNetworkInfo; + dust: number; feeHandler: () => Promise>; activityHandler: ( network: BaseNetwork, address: string ) => Promise; + apiType: typeof HaskoinAPI | typeof SSAPI; } +export const getAddress = (pubkey: string, network: BitcoinNetworkInfo) => { + if (pubkey.length < 64) return pubkey; + const { address } = payments[network.paymentType]({ + network, + pubkey: hexToBuffer(pubkey), + }); + return address as string; +}; export class BitcoinNetwork extends BaseNetwork { public assets: BaseToken[] = []; public networkInfo: BitcoinNetworkInfo; + public dust: number; private activityHandler: ( network: BaseNetwork, address: string @@ -52,23 +69,17 @@ export class BitcoinNetwork extends BaseNetwork { feeHandler: () => Promise>; constructor(options: BitcoinNetworkOptions) { const api = async () => { - const api = new BitcoinAPI(options.node, options.networkInfo); + const api = new options.apiType(options.node, options.networkInfo); await api.init(); - return api; + return api as BitcoinAPI; }; const baseOptions: BaseNetworkOptions = { identicon: createIcon, signer: [SignerType.secp256k1btc], provider: ProviderName.bitcoin, - displayAddress: (pubkey: string) => { - if (pubkey.length < 64) return pubkey; - const { address } = payments.p2wpkh({ - pubkey: hexToBuffer(pubkey), - network: options.networkInfo, - }); - return address as string; - }, + displayAddress: (pubkey: string) => + getAddress(pubkey, options.networkInfo), api, ...options, }; @@ -76,6 +87,7 @@ export class BitcoinNetwork extends BaseNetwork { this.activityHandler = options.activityHandler; this.networkInfo = options.networkInfo; this.feeHandler = options.feeHandler; + this.dust = options.dust; } public async getAllTokens(pubkey: string): Promise { diff --git a/packages/extension/src/providers/bitcoin/types/index.ts b/packages/extension/src/providers/bitcoin/types/index.ts index c6ba23bd2..d5c965487 100644 --- a/packages/extension/src/providers/bitcoin/types/index.ts +++ b/packages/extension/src/providers/bitcoin/types/index.ts @@ -1,5 +1,6 @@ import { NetworkNames } from "@enkryptcom/types"; import type { Provider as InjectedProvider } from "../inject"; +import { PaymentType } from "./bitcoin-network"; export const BitcoinNetworks = { BTC: NetworkNames.Bitcoin, @@ -17,6 +18,7 @@ export interface BitcoinNetworkInfo { scriptHash: number; wif: number; dustThreshold: null; + paymentType: PaymentType; } export interface HaskoinBalanceType { @@ -37,6 +39,14 @@ export interface HaskoinUnspentType { index: number; pkscript: string; value: number; + raw?: string; +} +export interface SSUnspentType { + txid: string; + vout: number; + value: string; + height: number; + confirmations: number; } export interface HaskoinTxType { txid: string; @@ -63,6 +73,28 @@ export interface HaskoinTxType { time: number; } +export interface SSTxType { + txid: string; + blockHash: string; + blockHeight: number; + timestamp: number; + confirmations: number; + fee: string; + hex: string; + vin: { + txid: string; + addresses?: string[]; + value: string; + }[]; + vout: { + addresses?: string[]; + value: string; + scriptPubKey: { + hex: string; + }; + }[]; +} + export interface RPCTxType { to: string; value: number; diff --git a/packages/extension/src/providers/bitcoin/ui/btc-verify-transaction.vue b/packages/extension/src/providers/bitcoin/ui/btc-verify-transaction.vue index b7a3a248b..7cffa490c 100644 --- a/packages/extension/src/providers/bitcoin/ui/btc-verify-transaction.vue +++ b/packages/extension/src/providers/bitcoin/ui/btc-verify-transaction.vue @@ -144,11 +144,11 @@ import { GasPriceTypes } from "@/providers/common/types"; import { RPCTxType } from "../types"; import BitcoinAPI from "@/providers/bitcoin/libs/api"; import { HaskoinUnspentType } from "../types"; -import { calculateSize } from "./libs/tx-size"; +import { calculateSizeBasedOnType } from "./libs/tx-size"; import { getGasCostValues } from "../libs/utils"; import { computed } from "@vue/reactivity"; import { toBN } from "web3-utils"; -import { BTCTxInfo } from "./types"; +import { getTxInfo as getBTCTxInfo } from "../libs/utils"; const isProcessing = ref(false); const isOpenSelectFee = ref(false); @@ -233,33 +233,17 @@ const updateUTXOs = async () => { const api = (await network.value.api()) as BitcoinAPI; return api.getUTXOs(account.value.address).then((utxos) => { accountUTXOs.value = utxos; - const txSize = calculateSize( - { - input_count: accountUTXOs.value.length, - }, - { - p2wpkh_output_count: 2, - } + const txSize = calculateSizeBasedOnType( + accountUTXOs.value.length, + 2, + (network.value as BitcoinNetwork).networkInfo.paymentType ); - setTransactionFees(Math.ceil(txSize.txVBytes)); + setTransactionFees(Math.ceil(txSize)); }); }; const getTxInfo = () => { - const txInfo: BTCTxInfo = { - inputs: [], - outputs: [], - }; - accountUTXOs.value.forEach((u) => { - txInfo.inputs.push({ - hash: u.txid, - index: u.index, - witnessUtxo: { - script: u.pkscript, - value: u.value, - }, - }); - }); + const txInfo = getBTCTxInfo(accountUTXOs.value); const balance = toBN(TokenBalance.value); const toAmount = toBN(tx.value.value.toString()); const currentFee = toBN( diff --git a/packages/extension/src/providers/bitcoin/ui/libs/signer.ts b/packages/extension/src/providers/bitcoin/ui/libs/signer.ts index a7a38b5e5..afe2e7098 100644 --- a/packages/extension/src/providers/bitcoin/ui/libs/signer.ts +++ b/packages/extension/src/providers/bitcoin/ui/libs/signer.ts @@ -5,6 +5,7 @@ import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger"; import { hexToBuffer } from "@enkryptcom/utils"; import { Psbt } from "bitcoinjs-lib"; import { signAsync } from "bitcoinjs-message"; +import { PaymentType } from "../../types/bitcoin-network"; const TransactionSigner = ( options: SignerTransactionOptions @@ -33,14 +34,26 @@ const TransactionSigner = ( }; const tx = new Psbt({ network: network.networkInfo }); payload.inputs - .map((u) => ({ - hash: u.hash, - index: u.index, - witnessUtxo: { - script: Buffer.from(u.witnessUtxo.script, "hex"), - value: u.witnessUtxo.value, - }, - })) + .map((u) => { + const res: { + hash: string; + index: number; + witnessUtxo?: { script: Buffer; value: number }; + nonWitnessUtxo?: Buffer; + } = { + hash: u.hash, + index: u.index, + }; + if (network.networkInfo.paymentType === PaymentType.P2WPKH) { + res.witnessUtxo = { + script: Buffer.from(u.witnessUtxo.script, "hex"), + value: u.witnessUtxo.value, + }; + } else if (network.networkInfo.paymentType === PaymentType.P2PKH) { + res.nonWitnessUtxo = Buffer.from(u.raw, "hex"); + } + return res; + }) .forEach((input) => tx.addInput(input)); payload.outputs.forEach((output) => tx.addOutput(output)); return tx.signAllInputsAsync(signer).then(() => { diff --git a/packages/extension/src/providers/bitcoin/ui/libs/tx-size.ts b/packages/extension/src/providers/bitcoin/ui/libs/tx-size.ts index 5c276cdce..4dcb707eb 100644 --- a/packages/extension/src/providers/bitcoin/ui/libs/tx-size.ts +++ b/packages/extension/src/providers/bitcoin/ui/libs/tx-size.ts @@ -1,6 +1,7 @@ // https://github.com/jlopp/bitcoin-transaction-size-calculator/blob/master/index.html import { toBN } from "web3-utils"; +import { PaymentType } from "../../types/bitcoin-network"; enum InputScriptType { P2PKH = "P2PKH", @@ -104,22 +105,24 @@ const getTxOverheadVBytes = ( ); }; +interface calcInputType { + input_script?: InputScriptType; + input_n?: number; + input_m?: number; + input_count: number; +} +interface calcOutputType { + p2pkh_output_count?: number; + p2sh_output_count?: number; + p2sh_p2wpkh_output_count?: number; + p2sh_p2wsh_output_count?: number; + p2wpkh_output_count?: number; + p2wsh_output_count?: number; + p2tr_output_count?: number; +} const calculateSize = ( - inputOptions: { - input_script?: InputScriptType; - input_n?: number; - input_m?: number; - input_count: number; - }, - outputOptions: { - p2pkh_output_count?: number; - p2sh_output_count?: number; - p2sh_p2wpkh_output_count?: number; - p2sh_p2wsh_output_count?: number; - p2wpkh_output_count?: number; - p2wsh_output_count?: number; - p2tr_output_count?: number; - } + inputOptions: calcInputType, + outputOptions: calcOutputType ) => { const defaultInputOptions = { input_script: InputScriptType.P2WPKH, @@ -225,12 +228,33 @@ const calculateSize = ( txVBytes + (inputWitnessSize * input_count * 3) / 4; const txWeight = txVBytes * 4; - return { txVBytes, txBytes, txWeight, }; }; - -export { InputScriptType, calculateSize }; +const calculateSizeBasedOnType = ( + numInputs: number, + numOutputs: number, + type: PaymentType +): number => { + const output: calcOutputType = {}; + if (type === PaymentType.P2PKH) { + output.p2pkh_output_count = numOutputs; + } else { + output.p2wpkh_output_count = numOutputs; + } + const size = calculateSize( + { + input_script: + type === PaymentType.P2PKH + ? InputScriptType.P2PKH + : InputScriptType.P2WPKH, + input_count: numInputs, + }, + output + ); + return type === PaymentType.P2PKH ? size.txBytes : size.txVBytes; +}; +export { InputScriptType, calculateSize, calculateSizeBasedOnType }; diff --git a/packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-alert.vue b/packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-alert.vue new file mode 100644 index 000000000..9f33862f8 --- /dev/null +++ b/packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-alert.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue b/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue index 85bab3bfb..b30e0dc2d 100644 --- a/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue @@ -51,11 +51,10 @@ v-if="isSendToken" :amount="amount" :fiat-value="selectedAsset.price" - :has-enough-balance="hasEnoughBalance" + :has-enough-balance="!nativeBalanceAfterTransaction.isNeg()" @update:input-amount="inputAmount" @update:input-set-max="setMaxValue" /> -
@@ -109,7 +111,7 @@ import SendAddressInput from "./components/send-address-input.vue"; import SendFromContactsList from "@/providers/common/ui/send-transaction/send-from-contacts-list.vue"; import SendContactsList from "@/providers/common/ui/send-transaction/send-contacts-list.vue"; import SendTokenSelect from "./components/send-token-select.vue"; -import SendAlert from "@/providers/common/ui/send-transaction/send-alert.vue"; +import SendAlert from "@/providers/bitcoin/ui/send-transaction/components/send-alert.vue"; import SendInputAmount from "@/providers/common/ui/send-transaction/send-input-amount.vue"; import SendFeeSelect from "@/providers/common/ui/send-transaction/send-fee-select.vue"; import TransactionFeeView from "@action/views/transaction-fee/index.vue"; @@ -132,9 +134,10 @@ import { BaseNetwork } from "@/types/base-network"; import { getGasCostValues, isAddress } from "../../libs/utils"; import BitcoinAPI from "@/providers/bitcoin/libs/api"; -import { calculateSize } from "../libs/tx-size"; +import { calculateSizeBasedOnType } from "../libs/tx-size"; import { HaskoinUnspentType } from "../../types"; -import { VerifyTransactionParams, BTCTxInfo } from "../types"; +import { VerifyTransactionParams } from "../types"; +import { getTxInfo as getBTCTxInfo } from "@/providers/bitcoin/libs/utils"; const props = defineProps({ network: { @@ -163,22 +166,6 @@ const selectedAsset = ref(loadingAsset); const amount = ref(""); const accountUTXOs = ref([]); -const hasEnoughBalance = computed(() => { - if (!isValidDecimals(sendAmount.value, selectedAsset.value.decimals!)) { - return false; - } - return toBN(selectedAsset.value.balance ?? "0").gte( - toBN(toBase(sendAmount.value ?? "0", selectedAsset.value.decimals!)).add( - toBN( - toBase( - gasCostValues.value[selectedFee.value].nativeValue, - selectedAsset.value.decimals! - ) - ) - ) - ); -}); - const sendAmount = computed(() => { if (amount.value && amount.value !== "") return amount.value; return "0"; @@ -193,27 +180,22 @@ const addressFrom = ref( const addressTo = ref(""); const isLoadingAssets = ref(true); -const nativeBalance = computed(() => { - const accountIndex = props.accountInfo.activeAccounts.findIndex( - (acc) => acc.address === addressFrom.value - ); - if (accountIndex !== -1) { - const balance = props.accountInfo.activeBalances[accountIndex]; - if (balance !== "~") { - return toBase(balance, props.network.decimals); - } - } - return "0"; -}); - onMounted(async () => { fetchAssets().then(setBaseCosts); }); const nativeBalanceAfterTransaction = computed(() => { - if (nativeBalance.value && amount.value !== "") { - const rawAmount = toBN(toBase(amount.value, selectedAsset.value.decimals!)); - return toBN(nativeBalance.value).sub(rawAmount); + if (selectedAsset.value) { + return toBN(selectedAsset.value.balance ?? "0").sub( + toBN(toBase(sendAmount.value ?? "0", selectedAsset.value.decimals!)).add( + toBN( + toBase( + gasCostValues.value[selectedFee.value].nativeValue, + selectedAsset.value.decimals! + ) + ) + ) + ); } return toBN(0); }); @@ -239,15 +221,12 @@ const updateUTXOs = async () => { const api = (await props.network.api()) as BitcoinAPI; return api.getUTXOs(addressFrom.value).then((utxos) => { accountUTXOs.value = utxos; - const txSize = calculateSize( - { - input_count: accountUTXOs.value.length, - }, - { - p2wpkh_output_count: 2, - } + const txSize = calculateSizeBasedOnType( + accountUTXOs.value.length, + 2, + (props.network as BitcoinNetwork).networkInfo.paymentType ); - setTransactionFees(Math.ceil(txSize.txVBytes)); + setTransactionFees(Math.ceil(txSize)); }); }; @@ -279,6 +258,8 @@ const isInputsValid = computed(() => { if (!isValidDecimals(sendAmount.value, selectedAsset.value.decimals!)) { return false; } + if (Number(sendAmount.value) < (props.network as BitcoinNetwork).dust) + return false; if (new BigNumber(sendAmount.value).gt(assetMaxValue.value)) return false; return true; }); @@ -369,22 +350,9 @@ const selectFee = (type: GasPriceTypes) => { const sendAction = async () => { const keyring = new PublicKeyRing(); const fromAccountInfo = await keyring.getAccount(addressFrom.value); - const txInfo: BTCTxInfo = { - inputs: [], - outputs: [], - }; - accountUTXOs.value.forEach((u) => { - txInfo.inputs.push({ - hash: u.txid, - index: u.index, - witnessUtxo: { - script: u.pkscript, - value: u.value, - }, - }); - }); + const txInfo = getBTCTxInfo(accountUTXOs.value); const balance = toBN(selectedAsset.value.balance!); - const toAmount = toBN(toBase(amount.value, selectedAsset.value.decimals)); + const toAmount = toBN(toBase(sendAmount.value, selectedAsset.value.decimals)); const currentFee = toBN( toBase( gasCostValues.value[selectedFee.value].nativeValue, @@ -499,4 +467,12 @@ const toggleSelector = (isTokenSend: boolean) => { } } } +p { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @error; + margin: 0; +} diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts index 4281a89a0..5eb13ce91 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts @@ -111,6 +111,10 @@ const supportedNetworks: Record = { tbName: "", cgPlatform: CoingeckoPlatform.TomoChain, }, + [NetworkNames.Shibarium]: { + tbName: "shib", + cgPlatform: CoingeckoPlatform.Shibarium, + }, }; const getTokens = ( diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts index 49da949bd..b3dd20211 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts @@ -24,6 +24,7 @@ const TokenList: Record = { [NetworkNames.Aurora]: `https://tokens.coingecko.com/${CoingeckoPlatform.Aurora}/all.json`, [NetworkNames.Celo]: `https://tokens.coingecko.com/${CoingeckoPlatform.Celo}/all.json`, [NetworkNames.TomoChain]: `https://tokens.coingecko.com/${CoingeckoPlatform.TomoChain}/all.json`, + [NetworkNames.Shibarium]: `https://tokens.coingecko.com/${CoingeckoPlatform.Shibarium}/all.json`, }; const getKnownNetworkTokens = async ( @@ -35,14 +36,16 @@ const getKnownNetworkTokens = async ( url: TokenList[networkName as SupportedNetworkNames], }, TOKEN_FETCH_TTL - ).then((json) => { - const tokens: CGToken[] = json.tokens; - const tObject: Record = {}; - tokens.forEach((t) => { - t.address = t.address.toLowerCase(); - tObject[t.address] = t; - }); - return tObject; - }); + ) + .then((json) => { + const tokens: CGToken[] = json.tokens; + const tObject: Record = {}; + tokens.forEach((t) => { + t.address = t.address.toLowerCase(); + tObject[t.address] = t; + }); + return tObject; + }) + .catch(() => ({})); }; export { TokenList, getKnownNetworkTokens }; diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts index 542e91097..5271ae82c 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts @@ -44,6 +44,7 @@ export type SupportedNetworkNames = | NetworkNames.Klaytn | NetworkNames.Aurora | NetworkNames.TomoChain + | NetworkNames.Shibarium | NetworkNames.MaticZK | NetworkNames.Celo | NetworkNames.ZkSync; diff --git a/packages/extension/src/providers/ethereum/networks/rsk.ts b/packages/extension/src/providers/ethereum/networks/rsk.ts index c07c52fcf..3661eea3f 100644 --- a/packages/extension/src/providers/ethereum/networks/rsk.ts +++ b/packages/extension/src/providers/ethereum/networks/rsk.ts @@ -2,8 +2,11 @@ import { CoingeckoPlatform, NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; import { EtherscanActivity } from "../libs/activity-handlers"; import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; -import { toChecksumAddress } from "ethereumjs-util"; -import { isAddress } from "web3-utils"; +import { + toChecksumAddress, + isValidChecksumAddress, + isValidAddress, +} from "ethereumjs-util"; import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; const rootstockOptions: EvmNetworkOptions = { @@ -29,7 +32,10 @@ rootstockOptions.displayAddress = (address: string) => { return toChecksumAddress(address, rootstockOptions.chainID); }; rootstockOptions.isAddress = (address: string) => { - return isAddress(address, parseInt(rootstockOptions.chainID)); + return ( + isValidAddress(address) || + isValidChecksumAddress(address, rootstockOptions.chainID) + ); }; const rootstock = new EvmNetwork(rootstockOptions); diff --git a/packages/extension/src/providers/ethereum/networks/shib.ts b/packages/extension/src/providers/ethereum/networks/shib.ts index 2d4b71f52..0c2e0f432 100644 --- a/packages/extension/src/providers/ethereum/networks/shib.ts +++ b/packages/extension/src/providers/ethereum/networks/shib.ts @@ -2,6 +2,7 @@ import { NetworkNames } from "@enkryptcom/types"; import { EvmNetwork, EvmNetworkOptions } from "../types/evm-network"; import { EtherscanActivity } from "../libs/activity-handlers"; import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; const shibOptions: EvmNetworkOptions = { name: NetworkNames.Shibarium, @@ -16,6 +17,7 @@ const shibOptions: EvmNetworkOptions = { node: "https://www.shibrpc.com", icon: require("./icons/shiba-inu.svg"), coingeckoID: "bone-shibaswap", + assetsInfoHandler, activityHandler: wrapActivityHandler(EtherscanActivity), }; diff --git a/packages/extension/src/ui/action/composables/account-info.ts b/packages/extension/src/ui/action/composables/account-info.ts index cd6581796..467654c0c 100644 --- a/packages/extension/src/ui/action/composables/account-info.ts +++ b/packages/extension/src/ui/action/composables/account-info.ts @@ -10,24 +10,31 @@ export default ( ) => { const marketData = new MarketData(); const fiatAmount = ref(defaultFiatVal); - const cryptoAmount = computed(() => { + + const cryptoAmountRaw = computed(() => { const selectedAccountIdx = accountInfo.value.activeAccounts.findIndex( (acc) => acc.address === accountInfo.value.selectedAccount?.address ); - let balance = "0"; if (selectedAccountIdx > -1) { - balance = accountInfo.value.activeBalances[selectedAccountIdx]; + const balance = accountInfo.value.activeBalances[selectedAccountIdx]; + return balance; } - return balance !== "~" ? formatFloatingPointValue(balance).value : balance; + return "0"; + }); + + const cryptoAmount = computed(() => { + return cryptoAmountRaw.value !== "~" + ? formatFloatingPointValue(cryptoAmountRaw.value).value + : cryptoAmountRaw.value; }); const updateFiatValues = async () => { fiatAmount.value = defaultFiatVal; - if (network.value.coingeckoID && cryptoAmount.value != "~") { + if (network.value.coingeckoID && cryptoAmountRaw.value != "~") { fiatAmount.value = `${ formatFiatValue( await marketData.getTokenValue( - cryptoAmount.value, + cryptoAmountRaw.value, network.value.coingeckoID, "USD" ) diff --git a/packages/extension/src/ui/action/icons/header/disconnect_icon.vue b/packages/extension/src/ui/action/icons/header/disconnect_icon.vue index ff76de632..e6e3e317c 100644 --- a/packages/extension/src/ui/action/icons/header/disconnect_icon.vue +++ b/packages/extension/src/ui/action/icons/header/disconnect_icon.vue @@ -1,27 +1,16 @@ diff --git a/packages/extension/src/ui/action/views/network-assets/components/custom-evm-token.vue b/packages/extension/src/ui/action/views/network-assets/components/custom-evm-token.vue index 160a774e2..142332884 100644 --- a/packages/extension/src/ui/action/views/network-assets/components/custom-evm-token.vue +++ b/packages/extension/src/ui/action/views/network-assets/components/custom-evm-token.vue @@ -141,7 +141,7 @@ watch([contractAddress, props], async () => { if (isValidAddress.value) { const api = (await props.network.api()) as API; - const info = await api.getTokenInfo(contractAddress.value!); + const info = await api.getTokenInfo(contractAddress.value!.toLowerCase()); if (info.name !== "Unknown") { let icon = props.network.icon; @@ -168,7 +168,7 @@ watch([contractAddress, props], async () => { symbol: info.symbol, decimals: info.decimals, icon, - contract: contractAddress.value!, + contract: contractAddress.value!.toLowerCase(), coingeckoID, }); diff --git a/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue b/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue index e64b24695..c0e831e84 100644 --- a/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue +++ b/packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue @@ -43,7 +43,7 @@
-

{{ token.balanceUSDf }}

+

${{ token.balanceUSDf }}

@{{ token.valuef }}

diff --git a/packages/extension/src/ui/action/views/swap/components/send-address-input.vue b/packages/extension/src/ui/action/views/swap/components/send-address-input.vue index d065b1d44..818fcdb7f 100644 --- a/packages/extension/src/ui/action/views/swap/components/send-address-input.vue +++ b/packages/extension/src/ui/action/views/swap/components/send-address-input.vue @@ -1,7 +1,7 @@