From 5499137cee07b275d6f2e56f75bd5c47e531ec32 Mon Sep 17 00:00:00 2001 From: S2kael Date: Fri, 17 May 2024 19:07:43 +0700 Subject: [PATCH 1/3] [Issue-80] Add logic filter utxos for transfer --- .../src/koni/background/handlers/Extension.ts | 3 +- .../helpers/balance/bitcoin.ts | 175 ++++++++++ .../helpers/{subscribe => balance}/evm.ts | 0 .../balance-service/helpers/balance/index.ts | 156 +++++++++ .../substrate/equilibrium.ts | 0 .../{subscribe => balance}/substrate/index.ts | 0 .../services/balance-service/helpers/index.ts | 2 +- .../helpers/subscribe/index.ts | 309 ------------------ .../helpers/transfer/bitcoin.ts | 3 +- 9 files changed, 336 insertions(+), 312 deletions(-) create mode 100644 packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts rename packages/extension-base/src/services/balance-service/helpers/{subscribe => balance}/evm.ts (100%) create mode 100644 packages/extension-base/src/services/balance-service/helpers/balance/index.ts rename packages/extension-base/src/services/balance-service/helpers/{subscribe => balance}/substrate/equilibrium.ts (100%) rename packages/extension-base/src/services/balance-service/helpers/{subscribe => balance}/substrate/index.ts (100%) delete mode 100644 packages/extension-base/src/services/balance-service/helpers/subscribe/index.ts diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 1b31533041..4d8487d7fa 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -25,6 +25,7 @@ import { getPSP34TransferExtrinsic } from '@subwallet/extension-base/koni/api/to import { createXcmExtrinsic } from '@subwallet/extension-base/koni/api/xcm'; import { YIELD_EXTRINSIC_TYPES } from '@subwallet/extension-base/koni/api/yield/helper/utils'; import KoniState from '@subwallet/extension-base/koni/background/handlers/State'; +import { getTransferableBitcoinUtxos } from '@subwallet/extension-base/services/balance-service/helpers/balance/bitcoin'; import { getBitcoinTransactionObject } from '@subwallet/extension-base/services/balance-service/helpers/transfer/bitcoin'; import { _API_OPTIONS_CHAIN_GROUP, _DEFAULT_MANTA_ZK_CHAIN, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX } from '@subwallet/extension-base/services/chain-service/constants'; import { _ChainApiStatus, _ChainConnectionStatus, _ChainState, _NetworkUpsertParams, _ValidateCustomAssetRequest, _ValidateCustomAssetResponse, EnableChainParams, EnableMultiChainParams } from '@subwallet/extension-base/services/chain-service/types'; @@ -2464,7 +2465,7 @@ export default class KoniExtension { const _feeCustom = feeCustom as BitcoinFeeRate; const combineFee = combineBitcoinFee(_fee, _feeOptions, _feeCustom); const bitcoinApi = this.#koniState.chainService.getBitcoinApi(chain); - const utxos = await bitcoinApi.api.getUtxos(address); + const utxos = await getTransferableBitcoinUtxos(bitcoinApi, address); const determineUtxosArgs: DetermineUtxosForSpendArgs = { amount: parseInt(value || '0'), feeRate: combineFee.feeRate, diff --git a/packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts b/packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts new file mode 100644 index 0000000000..9193d77c2e --- /dev/null +++ b/packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts @@ -0,0 +1,175 @@ +// Copyright 2019-2022 @subwallet/extension-base +// SPDX-License-Identifier: Apache-2.0 + +import { _AssetType, _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; +import { APIItemState } from '@subwallet/extension-base/background/KoniTypes'; +import { COMMON_REFRESH_BALANCE_INTERVAL } from '@subwallet/extension-base/constants'; +import { _BitcoinApi } from '@subwallet/extension-base/services/chain-service/types'; +import { _getChainNativeTokenSlug, _getRuneId, _isSupportRuneChain } from '@subwallet/extension-base/services/chain-service/utils'; +import { BalanceItem } from '@subwallet/extension-base/types'; +import { filterAssetsByChainAndType, filteredOutTxsUtxos, filterOutPendingTxsUtxos, getInscriptionUtxos, getRuneTxsUtxos } from '@subwallet/extension-base/utils'; +import BigN from 'bignumber.js'; + +// todo: update bitcoin params +function subscribeAddressesRuneInfo (bitcoinApi: _BitcoinApi, addresses: string[], assetMap: Record, chainInfo: _ChainInfo, callback: (rs: BalanceItem[]) => void) { + // todo: currently set decimal of runes on chain list to zero because the amount api return is after decimal + const chain = chainInfo.slug; + const tokenList = filterAssetsByChainAndType(assetMap, chain, [_AssetType.LOCAL]); + + // todo: check await asset ready before subscribe + if (Object.keys(tokenList).length === 0) { + // eslint-disable-next-line @typescript-eslint/no-empty-function + return () => { + }; + } + + const getRunesBalance = async () => { + const runeIdToSlugMap: Record = {}; + const runeIdToAllItemsMap: Record = {}; + + Object.values(tokenList).forEach((token) => { + runeIdToSlugMap[_getRuneId(token)] = token.slug; + }); + + // get runeId -> BalanceItem[] mapping + await Promise.all(addresses.map(async (address) => { + try { + const runes = await bitcoinApi.api.getRunes(address); + + runes.forEach((rune) => { + const runeId = rune.rune_id; + + const item = { + address: address, + tokenSlug: runeIdToSlugMap[runeId], + free: rune.amount, + locked: '0', + state: APIItemState.READY + } as BalanceItem; + + if (!runeIdToAllItemsMap[runeId]) { + runeIdToAllItemsMap[runeId] = []; + } + + runeIdToAllItemsMap[runeId].push(item); + }); + } catch (error) { + console.log(`Error on get runes balance of account ${address}`); + } + })); + + // callback balance batch items by tokenList + Object.values(runeIdToAllItemsMap).forEach((balanceItems) => { + callback(balanceItems); + }); + }; + + const fetchRuneBalances = () => { + getRunesBalance().catch(console.error); + }; + + fetchRuneBalances(); + const interval = setInterval(fetchRuneBalances, COMMON_REFRESH_BALANCE_INTERVAL); + + return () => { + clearInterval(interval); + }; +} + +export const getTransferableBitcoinUtxos = async (bitcoinApi: _BitcoinApi, address: string) => { + try { + const [utxos, txs, runeTxsUtxos, inscriptionUtxos] = await Promise.all([ + await bitcoinApi.api.getUtxos(address), + await bitcoinApi.api.getAddressTransaction(address), + await getRuneTxsUtxos(bitcoinApi, address), + await getInscriptionUtxos(bitcoinApi, address) + ]); + + // filter out pending utxos + let filteredUtxos = filterOutPendingTxsUtxos(address, txs, utxos); + + // filter out rune utxos + filteredUtxos = filteredOutTxsUtxos(filteredUtxos, runeTxsUtxos); + + // filter out inscription utxos + filteredUtxos = filteredOutTxsUtxos(filteredUtxos, inscriptionUtxos); + + return filteredUtxos; + } catch (error) { + console.log('Error while fetching Bitcoin balances', error); + + return []; + } +}; + +async function getBitcoinBalance (bitcoinApi: _BitcoinApi, addresses: string[]) { + return await Promise.all(addresses.map(async (address) => { + try { + const filteredUtxos = await getTransferableBitcoinUtxos(bitcoinApi, address); + + let balanceValue = new BigN(0); + + filteredUtxos.forEach((utxo) => { + balanceValue = balanceValue.plus(utxo.value); + }); + + return balanceValue.toString(); + } catch (error) { + console.log('Error while fetching Bitcoin balances', error); + + return '0'; + } + })); +} + +export function subscribeBitcoinBalance (addresses: string[], chainInfo: _ChainInfo, assetMap: Record, bitcoinApi: _BitcoinApi, callback: (rs: BalanceItem[]) => void): () => void { + const nativeSlug = _getChainNativeTokenSlug(chainInfo); + + const getBalance = () => { + getBitcoinBalance(bitcoinApi, addresses) + .then((balances) => { + return balances.map((balance, index): BalanceItem => { + return { + address: addresses[index], + tokenSlug: nativeSlug, + state: APIItemState.READY, + free: balance, + locked: '0' + }; + }); + }) + .catch((e) => { + console.error(`Error on get Bitcoin balance with token ${nativeSlug}`, e); + + return addresses.map((address): BalanceItem => { + return { + address: address, + tokenSlug: nativeSlug, + state: APIItemState.READY, + free: '0', + locked: '0' + }; + }); + }) + .then((items) => { + callback(items); + }) + .catch(console.error); + }; + + getBalance(); + const interval = setInterval(getBalance, COMMON_REFRESH_BALANCE_INTERVAL); + + if (_isSupportRuneChain(chainInfo.slug)) { + const unsub = subscribeAddressesRuneInfo(bitcoinApi, addresses, assetMap, chainInfo, callback); + + return () => { + clearInterval(interval); + unsub && unsub(); + }; + } else { + return () => { + clearInterval(interval); + }; + } +} diff --git a/packages/extension-base/src/services/balance-service/helpers/subscribe/evm.ts b/packages/extension-base/src/services/balance-service/helpers/balance/evm.ts similarity index 100% rename from packages/extension-base/src/services/balance-service/helpers/subscribe/evm.ts rename to packages/extension-base/src/services/balance-service/helpers/balance/evm.ts diff --git a/packages/extension-base/src/services/balance-service/helpers/balance/index.ts b/packages/extension-base/src/services/balance-service/helpers/balance/index.ts new file mode 100644 index 0000000000..2d21ae2e80 --- /dev/null +++ b/packages/extension-base/src/services/balance-service/helpers/balance/index.ts @@ -0,0 +1,156 @@ +// Copyright 2019-2022 @subwallet/extension-base +// SPDX-License-Identifier: Apache-2.0 + +import { _AssetType, _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; +import { APIItemState } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountJson } from '@subwallet/extension-base/background/types'; +import { _BitcoinApi, _EvmApi, _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; +import { _isPureBitcoinChain, _isPureEvmChain } from '@subwallet/extension-base/services/chain-service/utils'; +import { BalanceItem } from '@subwallet/extension-base/types'; +import { filterAssetsByChainAndType } from '@subwallet/extension-base/utils'; +import { getKeypairTypeByAddress, isBitcoinAddress } from '@subwallet/keyring'; +import keyring from '@subwallet/ui-keyring'; + +import { noop } from '@polkadot/util'; + +import { subscribeBitcoinBalance } from './bitcoin'; +import { subscribeEVMBalance } from './evm'; +import { subscribeSubstrateBalance } from './substrate'; + +/** + * @function getAccountJsonByAddress + * @desc Get account info by address + *

+ * Note: Use on the background only + *

+ * @param {string} address - Address + * @returns {AccountJson|null} - Account info or null if not found + */ +export const getAccountJsonByAddress = (address: string): AccountJson | null => { + try { + const pair = keyring.getPair(address); + + if (pair) { + return { + address: pair.address, + type: pair.type, + ...pair.meta + }; + } else { + return null; + } + } catch (e) { + console.warn(e); + + return null; + } +}; + +/** Filter addresses to subscribe by chain info */ +const filterAddress = (addresses: string[], chainInfo: _ChainInfo): [string[], string[]] => { + const isEvmChain = _isPureEvmChain(chainInfo); + const isBitcoinChain = _isPureBitcoinChain(chainInfo); + const useAddresses: string[] = []; + const notSupportAddresses: string[] = []; + + if (isEvmChain) { + addresses.forEach((a) => { + getKeypairTypeByAddress(a) === 'ethereum' ? useAddresses.push(a) : notSupportAddresses.push(a); + }); + } else if (isBitcoinChain) { + addresses.forEach((address) => { + const bitcoinAddressNetwork = isBitcoinAddress(address); + + if ((bitcoinAddressNetwork === 'mainnet' && chainInfo.slug === 'bitcoin') || (bitcoinAddressNetwork === 'testnet' && chainInfo.slug === 'bitcoinTestnet')) { + useAddresses.push(address); + } else { + notSupportAddresses.push(address); + } + }); + } else { + notSupportAddresses.push(...addresses); + } + + return [useAddresses, notSupportAddresses]; +}; + +// main subscription, use for multiple chains, multiple addresses and multiple tokens +export function subscribeBalance ( + addresses: string[], + chains: string[], + tokens: string[], + _chainAssetMap: Record, + _chainInfoMap: Record, + substrateApiMap: Record, + evmApiMap: Record, + bitcoinApiMap: Record, + callback: (rs: BalanceItem[]) => void) { + // Filter chain and token + const chainAssetMap: Record = Object.fromEntries(Object.entries(_chainAssetMap).filter(([token]) => tokens.includes(token))); + const chainInfoMap: Record = Object.fromEntries(Object.entries(_chainInfoMap).filter(([chain]) => chains.includes(chain))); + + // Looping over each chain + const unsubList = Object.values(chainInfoMap).map(async (chainInfo) => { + const chainSlug = chainInfo.slug; + const [useAddresses, notSupportAddresses] = filterAddress(addresses, chainInfo); + + if (notSupportAddresses.length) { + const tokens = filterAssetsByChainAndType(chainAssetMap, chainSlug, [_AssetType.NATIVE, _AssetType.ERC20, _AssetType.PSP22, _AssetType.LOCAL]); + + const now = new Date().getTime(); + + Object.values(tokens).forEach((token) => { + const items: BalanceItem[] = notSupportAddresses.map((address): BalanceItem => ({ + address, + tokenSlug: token.slug, + free: '0', + locked: '0', + state: APIItemState.NOT_SUPPORT, + timestamp: now + })); + + callback(items); + }); + } + + if (!useAddresses.length) { + return noop; + } + + const evmApi = evmApiMap[chainSlug]; + + if (_isPureEvmChain(chainInfo)) { + return subscribeEVMBalance({ + addresses: useAddresses, + assetMap: chainAssetMap, + callback, + chainInfo, + evmApi + }); + } + + const bitcoinApi = bitcoinApiMap[chainSlug]; + + if (_isPureBitcoinChain(chainInfo)) { + return subscribeBitcoinBalance( + useAddresses, + chainInfo, + chainAssetMap, + bitcoinApi, + callback + ); + } + + const substrateApi = await substrateApiMap[chainSlug].isReady; + + return subscribeSubstrateBalance(useAddresses, chainInfo, chainAssetMap, substrateApi, evmApi, callback); + }); + + return () => { + unsubList.forEach((subProm) => { + subProm.then((unsub) => { + unsub && unsub(); + }).catch(console.error); + }); + }; +} diff --git a/packages/extension-base/src/services/balance-service/helpers/subscribe/substrate/equilibrium.ts b/packages/extension-base/src/services/balance-service/helpers/balance/substrate/equilibrium.ts similarity index 100% rename from packages/extension-base/src/services/balance-service/helpers/subscribe/substrate/equilibrium.ts rename to packages/extension-base/src/services/balance-service/helpers/balance/substrate/equilibrium.ts diff --git a/packages/extension-base/src/services/balance-service/helpers/subscribe/substrate/index.ts b/packages/extension-base/src/services/balance-service/helpers/balance/substrate/index.ts similarity index 100% rename from packages/extension-base/src/services/balance-service/helpers/subscribe/substrate/index.ts rename to packages/extension-base/src/services/balance-service/helpers/balance/substrate/index.ts diff --git a/packages/extension-base/src/services/balance-service/helpers/index.ts b/packages/extension-base/src/services/balance-service/helpers/index.ts index 7ac083f9be..efff9e8a26 100644 --- a/packages/extension-base/src/services/balance-service/helpers/index.ts +++ b/packages/extension-base/src/services/balance-service/helpers/index.ts @@ -2,5 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 export * from './group'; -export * from './subscribe'; +export * from './balance'; export * from './transfer'; diff --git a/packages/extension-base/src/services/balance-service/helpers/subscribe/index.ts b/packages/extension-base/src/services/balance-service/helpers/subscribe/index.ts deleted file mode 100644 index 8462de1a32..0000000000 --- a/packages/extension-base/src/services/balance-service/helpers/subscribe/index.ts +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2019-2022 @subwallet/extension-base -// SPDX-License-Identifier: Apache-2.0 - -import { _AssetType, _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; -import { APIItemState } from '@subwallet/extension-base/background/KoniTypes'; -import { AccountJson } from '@subwallet/extension-base/background/types'; -import { COMMON_REFRESH_BALANCE_INTERVAL } from '@subwallet/extension-base/constants'; -import { _BitcoinApi, _EvmApi, _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _getChainNativeTokenSlug, _getRuneId, _isPureBitcoinChain, _isPureEvmChain, _isSupportRuneChain } from '@subwallet/extension-base/services/chain-service/utils'; -import { BalanceItem } from '@subwallet/extension-base/types'; -import { filterAssetsByChainAndType, filteredOutTxsUtxos, filterOutPendingTxsUtxos, getInscriptionUtxos, getRuneTxsUtxos } from '@subwallet/extension-base/utils'; -import { getKeypairTypeByAddress, isBitcoinAddress } from '@subwallet/keyring'; -import keyring from '@subwallet/ui-keyring'; -import BigN from 'bignumber.js'; - -import { noop } from '@polkadot/util'; - -import { subscribeEVMBalance } from './evm'; -import { subscribeSubstrateBalance } from './substrate'; - -/** - * @function getAccountJsonByAddress - * @desc Get account info by address - *

- * Note: Use on the background only - *

- * @param {string} address - Address - * @returns {AccountJson|null} - Account info or null if not found - */ -export const getAccountJsonByAddress = (address: string): AccountJson | null => { - try { - const pair = keyring.getPair(address); - - if (pair) { - return { - address: pair.address, - type: pair.type, - ...pair.meta - }; - } else { - return null; - } - } catch (e) { - console.warn(e); - - return null; - } -}; - -/** Filter addresses to subscribe by chain info */ -const filterAddress = (addresses: string[], chainInfo: _ChainInfo): [string[], string[]] => { - const isEvmChain = _isPureEvmChain(chainInfo); - const isBitcoinChain = _isPureBitcoinChain(chainInfo); - const useAddresses: string[] = []; - const notSupportAddresses: string[] = []; - - if (isEvmChain) { - addresses.forEach((a) => { - getKeypairTypeByAddress(a) === 'ethereum' ? useAddresses.push(a) : notSupportAddresses.push(a); - }); - } else if (isBitcoinChain) { - addresses.forEach((address) => { - const bitcoinAddressNetwork = isBitcoinAddress(address); - - if ((bitcoinAddressNetwork === 'mainnet' && chainInfo.slug === 'bitcoin') || (bitcoinAddressNetwork === 'testnet' && chainInfo.slug === 'bitcoinTestnet')) { - useAddresses.push(address); - } else { - notSupportAddresses.push(address); - } - }); - } else { - notSupportAddresses.push(...addresses); - } - - return [useAddresses, notSupportAddresses]; -}; - -// todo: update bitcoin params -function subscribeAddressesRuneInfo (bitcoinApi: _BitcoinApi, addresses: string[], assetMap: Record, chainInfo: _ChainInfo, callback: (rs: BalanceItem[]) => void) { - // todo: currently set decimal of runes on chain list to zero because the amount api return is after decimal - const chain = chainInfo.slug; - const tokenList = filterAssetsByChainAndType(assetMap, chain, [_AssetType.LOCAL]); - - // todo: check await asset ready before subscribe - if (Object.keys(tokenList).length === 0) { - // eslint-disable-next-line @typescript-eslint/no-empty-function - return () => { - }; - } - - const getRunesBalance = async () => { - const runeIdToSlugMap: Record = {}; - const runeIdToAllItemsMap: Record = {}; - - Object.values(tokenList).forEach((token) => { - runeIdToSlugMap[_getRuneId(token)] = token.slug; - }); - - // get runeId -> BalanceItem[] mapping - await Promise.all(addresses.map(async (address) => { - try { - const runes = await bitcoinApi.api.getRunes(address); - - runes.forEach((rune) => { - const runeId = rune.rune_id; - - const item = { - address: address, - tokenSlug: runeIdToSlugMap[runeId], - free: rune.amount, - locked: '0', - state: APIItemState.READY - } as BalanceItem; - - if (!runeIdToAllItemsMap[runeId]) { - runeIdToAllItemsMap[runeId] = []; - } - - runeIdToAllItemsMap[runeId].push(item); - }); - } catch (error) { - console.log(`Error on get runes balance of account ${address}`); - } - })); - - // callback balance batch items by tokenList - Object.values(runeIdToAllItemsMap).forEach((balanceItems) => { - callback(balanceItems); - }); - }; - - const fetchRuneBalances = () => { - getRunesBalance().catch(console.error); - }; - - fetchRuneBalances(); - const interval = setInterval(fetchRuneBalances, COMMON_REFRESH_BALANCE_INTERVAL); - - return () => { - clearInterval(interval); - }; -} - -async function getBitcoinBalance (bitcoinApi: _BitcoinApi, addresses: string[]) { - return await Promise.all(addresses.map(async (address) => { - try { - const [utxos, txs, runeTxsUtxos, inscriptionUtxos] = await Promise.all([ - await bitcoinApi.api.getUtxos(address), - await bitcoinApi.api.getAddressTransaction(address), - await getRuneTxsUtxos(bitcoinApi, address), - await getInscriptionUtxos(bitcoinApi, address) - ]); - - // filter out pending utxos - let filteredUtxos = filterOutPendingTxsUtxos(address, txs, utxos); - - // filter out rune utxos - filteredUtxos = filteredOutTxsUtxos(filteredUtxos, runeTxsUtxos); - - // filter out inscription utxos - filteredUtxos = filteredOutTxsUtxos(filteredUtxos, inscriptionUtxos); - - let balanceValue = new BigN(0); - - filteredUtxos.forEach((utxo) => { - balanceValue = balanceValue.plus(utxo.value); - }); - - return balanceValue.toString(); - } catch (error) { - console.log('Error while fetching Bitcoin balances', error); - - return '0'; - } - })); -} - -function subscribeBitcoinBalance (addresses: string[], chainInfo: _ChainInfo, assetMap: Record, bitcoinApi: _BitcoinApi, callback: (rs: BalanceItem[]) => void): () => void { - const nativeSlug = _getChainNativeTokenSlug(chainInfo); - - const getBalance = () => { - getBitcoinBalance(bitcoinApi, addresses) - .then((balances) => { - return balances.map((balance, index): BalanceItem => { - return { - address: addresses[index], - tokenSlug: nativeSlug, - state: APIItemState.READY, - free: balance, - locked: '0' - }; - }); - }) - .catch((e) => { - console.error(`Error on get Bitcoin balance with token ${nativeSlug}`, e); - - return addresses.map((address): BalanceItem => { - return { - address: address, - tokenSlug: nativeSlug, - state: APIItemState.READY, - free: '0', - locked: '0' - }; - }); - }) - .then((items) => { - callback(items); - }) - .catch(console.error); - }; - - getBalance(); - const interval = setInterval(getBalance, COMMON_REFRESH_BALANCE_INTERVAL); - - if (_isSupportRuneChain(chainInfo.slug)) { - const unsub = subscribeAddressesRuneInfo(bitcoinApi, addresses, assetMap, chainInfo, callback); - - return () => { - clearInterval(interval); - unsub && unsub(); - }; - } else { - return () => { - clearInterval(interval); - }; - } -} - -// main subscription, use for multiple chains, multiple addresses and multiple tokens -export function subscribeBalance ( - addresses: string[], - chains: string[], - tokens: string[], - _chainAssetMap: Record, - _chainInfoMap: Record, - substrateApiMap: Record, - evmApiMap: Record, - bitcoinApiMap: Record, - callback: (rs: BalanceItem[]) => void) { - // Filter chain and token - const chainAssetMap: Record = Object.fromEntries(Object.entries(_chainAssetMap).filter(([token]) => tokens.includes(token))); - const chainInfoMap: Record = Object.fromEntries(Object.entries(_chainInfoMap).filter(([chain]) => chains.includes(chain))); - - // Looping over each chain - const unsubList = Object.values(chainInfoMap).map(async (chainInfo) => { - const chainSlug = chainInfo.slug; - const [useAddresses, notSupportAddresses] = filterAddress(addresses, chainInfo); - - if (notSupportAddresses.length) { - const tokens = filterAssetsByChainAndType(chainAssetMap, chainSlug, [_AssetType.NATIVE, _AssetType.ERC20, _AssetType.PSP22, _AssetType.LOCAL]); - - const now = new Date().getTime(); - - Object.values(tokens).forEach((token) => { - const items: BalanceItem[] = notSupportAddresses.map((address): BalanceItem => ({ - address, - tokenSlug: token.slug, - free: '0', - locked: '0', - state: APIItemState.NOT_SUPPORT, - timestamp: now - })); - - callback(items); - }); - } - - if (!useAddresses.length) { - return noop; - } - - const evmApi = evmApiMap[chainSlug]; - - if (_isPureEvmChain(chainInfo)) { - return subscribeEVMBalance({ - addresses: useAddresses, - assetMap: chainAssetMap, - callback, - chainInfo, - evmApi - }); - } - - const bitcoinApi = bitcoinApiMap[chainSlug]; - - if (_isPureBitcoinChain(chainInfo)) { - return subscribeBitcoinBalance( - useAddresses, - chainInfo, - chainAssetMap, - bitcoinApi, - callback - ); - } - - const substrateApi = await substrateApiMap[chainSlug].isReady; - - return subscribeSubstrateBalance(useAddresses, chainInfo, chainAssetMap, substrateApi, evmApi, callback); - }); - - return () => { - unsubList.forEach((subProm) => { - subProm.then((unsub) => { - unsub && unsub(); - }).catch(console.error); - }); - }; -} diff --git a/packages/extension-base/src/services/balance-service/helpers/transfer/bitcoin.ts b/packages/extension-base/src/services/balance-service/helpers/transfer/bitcoin.ts index 8ce153c3c7..501868e149 100644 --- a/packages/extension-base/src/services/balance-service/helpers/transfer/bitcoin.ts +++ b/packages/extension-base/src/services/balance-service/helpers/transfer/bitcoin.ts @@ -1,6 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-base // SPDX-License-Identifier: Apache-2.0 +import { getTransferableBitcoinUtxos } from '@subwallet/extension-base/services/balance-service/helpers/balance/bitcoin'; import { _BitcoinApi } from '@subwallet/extension-base/services/chain-service/types'; import { BitcoinFeeInfo, BitcoinFeeRate, GetFeeFunction, TransactionFee } from '@subwallet/extension-base/types'; import { combineBitcoinFee, determineUtxosForSpend, determineUtxosForSpendAll, getId } from '@subwallet/extension-base/utils'; @@ -33,7 +34,7 @@ export async function getBitcoinTransactionObject ({ bitcoinApi, const feeCustom = _feeCustom as BitcoinFeeRate; const feeInfo = await getChainFee(id, chain, 'bitcoin') as BitcoinFeeInfo; const bitcoinFee = combineBitcoinFee(feeInfo, feeOption, feeCustom); - const utxos = await bitcoinApi.api.getUtxos(from); + const utxos = await getTransferableBitcoinUtxos(bitcoinApi, from); try { const amountValue = parseFloat(value); From a11c004cc9539316f13d0cc9ad41beed580d78d3 Mon Sep 17 00:00:00 2001 From: S2kael Date: Fri, 24 May 2024 11:25:23 +0700 Subject: [PATCH 2/3] [Issue-80] Resolve conflict --- .../helpers/balance/bitcoin.ts | 78 +++++++++++++++---- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts b/packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts index 9193d77c2e..ae75bb5d31 100644 --- a/packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts +++ b/packages/extension-base/src/services/balance-service/helpers/balance/bitcoin.ts @@ -4,17 +4,17 @@ import { _AssetType, _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; import { APIItemState } from '@subwallet/extension-base/background/KoniTypes'; import { COMMON_REFRESH_BALANCE_INTERVAL } from '@subwallet/extension-base/constants'; +import { Brc20BalanceItem } from '@subwallet/extension-base/services/chain-service/handler/bitcoin/strategy/BlockStream/types'; import { _BitcoinApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _getChainNativeTokenSlug, _getRuneId, _isSupportRuneChain } from '@subwallet/extension-base/services/chain-service/utils'; +import { _getChainNativeTokenSlug, _getRuneId } from '@subwallet/extension-base/services/chain-service/utils'; import { BalanceItem } from '@subwallet/extension-base/types'; import { filterAssetsByChainAndType, filteredOutTxsUtxos, filterOutPendingTxsUtxos, getInscriptionUtxos, getRuneTxsUtxos } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; // todo: update bitcoin params -function subscribeAddressesRuneInfo (bitcoinApi: _BitcoinApi, addresses: string[], assetMap: Record, chainInfo: _ChainInfo, callback: (rs: BalanceItem[]) => void) { - // todo: currently set decimal of runes on chain list to zero because the amount api return is after decimal +function subscribeRuneBalance (bitcoinApi: _BitcoinApi, addresses: string[], assetMap: Record, chainInfo: _ChainInfo, callback: (rs: BalanceItem[]) => void) { const chain = chainInfo.slug; - const tokenList = filterAssetsByChainAndType(assetMap, chain, [_AssetType.LOCAL]); + const tokenList = filterAssetsByChainAndType(assetMap, chain, [_AssetType.RUNE]); // todo: check await asset ready before subscribe if (Object.keys(tokenList).length === 0) { @@ -39,6 +39,10 @@ function subscribeAddressesRuneInfo (bitcoinApi: _BitcoinApi, addresses: string[ runes.forEach((rune) => { const runeId = rune.rune_id; + if (!Object.keys(runeIdToSlugMap).includes(runeId)) { + return; + } + const item = { address: address, tokenSlug: runeIdToSlugMap[runeId], @@ -76,6 +80,53 @@ function subscribeAddressesRuneInfo (bitcoinApi: _BitcoinApi, addresses: string[ }; } +function subscribeBRC20Balance (bitcoinApi: _BitcoinApi, addresses: string[], assetMap: Record, chainInfo: _ChainInfo, callback: (rs: BalanceItem[]) => void) { + const chain = chainInfo.slug; + const tokenList = filterAssetsByChainAndType(assetMap, chain, [_AssetType.BRC20]); + + const getBRC20Balance = () => { + Object.values(tokenList).map(async (token) => { + try { + const ticker = token.symbol; + const balances: Brc20BalanceItem[] = await Promise.all(addresses.map(async (address) => { + try { + return await bitcoinApi.api.getAddressBRC20FreeLockedBalance(address, ticker); + } catch (error) { + console.error(`Error on get BRC balance of account ${address} for token ${token.slug}`, error); + + return { + free: '0', + locked: '0' + }; + } + })); + + const items: BalanceItem[] = balances.map((balance, index): BalanceItem => { + return { + address: addresses[index], + tokenSlug: token.slug, + free: balance.free || '0', + locked: balance.locked || '0', + state: APIItemState.READY + }; + }); + + callback(items); + } catch (error) { + console.log(token.slug, error); + } + }); + }; + + getBRC20Balance(); + + const interval = setInterval(getBRC20Balance, COMMON_REFRESH_BALANCE_INTERVAL); + + return () => { + clearInterval(interval); + }; +} + export const getTransferableBitcoinUtxos = async (bitcoinApi: _BitcoinApi, address: string) => { try { const [utxos, txs, runeTxsUtxos, inscriptionUtxos] = await Promise.all([ @@ -158,18 +209,15 @@ export function subscribeBitcoinBalance (addresses: string[], chainInfo: _ChainI }; getBalance(); + const interval = setInterval(getBalance, COMMON_REFRESH_BALANCE_INTERVAL); - if (_isSupportRuneChain(chainInfo.slug)) { - const unsub = subscribeAddressesRuneInfo(bitcoinApi, addresses, assetMap, chainInfo, callback); + const unsub = subscribeRuneBalance(bitcoinApi, addresses, assetMap, chainInfo, callback); + const unsub2 = subscribeBRC20Balance(bitcoinApi, addresses, assetMap, chainInfo, callback); - return () => { - clearInterval(interval); - unsub && unsub(); - }; - } else { - return () => { - clearInterval(interval); - }; - } + return () => { + clearInterval(interval); + unsub && unsub(); + unsub2 && unsub2(); + }; } From 8d1ec76de0d5348f9bc0ac62068d9ab9079cf2c1 Mon Sep 17 00:00:00 2001 From: S2kael Date: Fri, 24 May 2024 12:00:00 +0700 Subject: [PATCH 3/3] [Issue-66] Show max transfer button --- .../src/Popup/Transaction/variants/SendFund.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx index ff63556e85..26058892c0 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -712,7 +712,7 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { forceUpdateMaxValue={forceUpdateMaxValue} maxValue={transferInfo?.maxTransferable || '0'} onSetMax={onSetMaxTransferable} - // showMaxButton={!hideMaxButton && !!transferInfo} + showMaxButton={!hideMaxButton && !!transferInfo} tooltip={t('Amount')} />