Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue-80] Add logic filter utxos for transfer #95

Merged
merged 4 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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, _ValidateCustomBrc20Request, _ValidateCustomBrc20Response, _ValidateCustomRuneRequest, _ValidateCustomRuneResponse, EnableChainParams, EnableMultiChainParams } from '@subwallet/extension-base/services/chain-service/types';
Expand Down Expand Up @@ -2473,7 +2474,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,79 +3,14 @@

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 { Brc20BalanceItem } from '@subwallet/extension-base/services/chain-service/handler/bitcoin/strategy/BlockStream/types';
import { _BitcoinApi, _EvmApi, _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types';
import { _getChainNativeTokenSlug, _getRuneId, _isPureBitcoinChain, _isPureEvmChain } from '@subwallet/extension-base/services/chain-service/utils';
import { _BitcoinApi } from '@subwallet/extension-base/services/chain-service/types';
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 { 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
* <p>
* Note: Use on the background only
* </p>
* @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 subscribeRuneBalance (bitcoinApi: _BitcoinApi, addresses: string[], assetMap: Record<string, _ChainAsset>, chainInfo: _ChainInfo, callback: (rs: BalanceItem[]) => void) {
const chain = chainInfo.slug;
Expand Down Expand Up @@ -192,24 +127,36 @@ function subscribeBRC20Balance (bitcoinApi: _BitcoinApi, addresses: string[], as
};
}

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)
]);
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 pending utxos
let filteredUtxos = filterOutPendingTxsUtxos(address, txs, utxos);

// filter out rune utxos
filteredUtxos = filteredOutTxsUtxos(filteredUtxos, runeTxsUtxos);
// filter out rune utxos
filteredUtxos = filteredOutTxsUtxos(filteredUtxos, runeTxsUtxos);

// filter out inscription utxos
filteredUtxos = filteredOutTxsUtxos(filteredUtxos, inscriptionUtxos);
// 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);

Expand All @@ -226,7 +173,7 @@ async function getBitcoinBalance (bitcoinApi: _BitcoinApi, addresses: string[])
}));
}

function subscribeBitcoinBalance (addresses: string[], chainInfo: _ChainInfo, assetMap: Record<string, _ChainAsset>, bitcoinApi: _BitcoinApi, callback: (rs: BalanceItem[]) => void): () => void {
export function subscribeBitcoinBalance (addresses: string[], chainInfo: _ChainInfo, assetMap: Record<string, _ChainAsset>, bitcoinApi: _BitcoinApi, callback: (rs: BalanceItem[]) => void): () => void {
const nativeSlug = _getChainNativeTokenSlug(chainInfo);

const getBalance = () => {
Expand Down Expand Up @@ -274,84 +221,3 @@ function subscribeBitcoinBalance (addresses: string[], chainInfo: _ChainInfo, as
unsub2 && unsub2();
};
}

// main subscription, use for multiple chains, multiple addresses and multiple tokens
export function subscribeBalance (
addresses: string[],
chains: string[],
tokens: string[],
_chainAssetMap: Record<string, _ChainAsset>,
_chainInfoMap: Record<string, _ChainInfo>,
substrateApiMap: Record<string, _SubstrateApi>,
evmApiMap: Record<string, _EvmApi>,
bitcoinApiMap: Record<string, _BitcoinApi>,
callback: (rs: BalanceItem[]) => void) {
// Filter chain and token
const chainAssetMap: Record<string, _ChainAsset> = Object.fromEntries(Object.entries(_chainAssetMap).filter(([token]) => tokens.includes(token)));
const chainInfoMap: Record<string, _ChainInfo> = 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);
});
};
}
Loading
Loading