From 14f6e3c702fc9af0303db6ce52dc550bd519ed7a Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Tue, 2 Jul 2024 18:04:11 +0700 Subject: [PATCH 01/11] [Issue 228] Update input outpu for signPsbt --- .../src/background/KoniTypes.ts | 14 ++++-- .../src/koni/background/handlers/State.ts | 37 +++++++++++++--- .../src/koni/background/handlers/Tabs.ts | 15 +++++-- .../variants/BitcoinSignPsbtConfirmation.tsx | 43 ++++++++++++++++--- 4 files changed, 89 insertions(+), 20 deletions(-) diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index 89c710bba73..db53bb3340d 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -1,7 +1,7 @@ // Copyright 2019-2022 @polkadot/extension-koni authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { Psbt, PsbtTxInput, PsbtTxOutput } from 'bitcoinjs-lib'; +import type { Psbt } from 'bitcoinjs-lib'; import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _FundStatus, _MultiChainAsset } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; @@ -1344,10 +1344,16 @@ export interface BitcoinSendTransactionParams { recipients: BitcoinRecipientTransactionParams[] } +export interface PsbtTransactionArg { + address?: string; + amount?: string; +} + export interface BitcoinSignPsbtPayload extends Omit{ - txInput: PsbtTxInput[]; - txOutput: PsbtTxOutput[]; - psbt: Psbt + txInput: PsbtTransactionArg[]; + txOutput: PsbtTransactionArg[]; + psbt: Psbt; + tokenSlug: string; } enum SignatureHash { diff --git a/packages/extension-base/src/koni/background/handlers/State.ts b/packages/extension-base/src/koni/background/handlers/State.ts index 97d67c5c4bb..7f66ac4c460 100644 --- a/packages/extension-base/src/koni/background/handlers/State.ts +++ b/packages/extension-base/src/koni/background/handlers/State.ts @@ -6,7 +6,7 @@ import { BitcoinProviderError } from '@subwallet/extension-base/background/error import { EvmProviderError } from '@subwallet/extension-base/background/errors/EvmProviderError'; import { withErrorLog } from '@subwallet/extension-base/background/handlers/helpers'; import { isSubscriptionRunning, unsubscribe } from '@subwallet/extension-base/background/handlers/subscriptions'; -import { AccountRefMap, AddTokenRequestExternal, AmountData, APIItemState, ApiMap, AuthRequestV2, BasicTxErrorType, BitcoinOutputUtox, BitcoinProviderErrorType, BitcoinSendTransactionParams, BitcoinSendTransactionRequest, BitcoinSignatureRequest, BitcoinSignPsbtPayload, BitcoinSignPsbtRawRequest, BitcoinSignPsbtRequest, BitcoinTransactionConfig, ChainStakingMetadata, ChainType, ConfirmationsQueue, CrowdloanItem, CrowdloanJson, CurrentAccountInfo, CurrentAccountProxyInfo, EvmProviderErrorType, EvmSendTransactionParams, EvmSendTransactionRequest, EvmSignatureRequest, ExternalRequestPromise, ExternalRequestPromiseStatus, ExtrinsicType, MantaAuthorizationContext, MantaPayConfig, MantaPaySyncState, NftCollection, NftItem, NftJson, NominatorMetadata, RequestAccountExportPrivateKey, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestCrowdloanContributions, RequestSettingsType, ResponseAccountExportPrivateKey, ResponseCheckPublicAndSecretKey, ServiceInfo, SignMessageBitcoinResult, SignPsbtBitcoinResult, SingleModeJson, StakingItem, StakingJson, StakingRewardItem, StakingRewardJson, StakingType, UiSettings } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountRefMap, AddTokenRequestExternal, AmountData, APIItemState, ApiMap, AuthRequestV2, BasicTxErrorType, BitcoinOutputUtox, BitcoinProviderErrorType, BitcoinSendTransactionParams, BitcoinSendTransactionRequest, BitcoinSignatureRequest, BitcoinSignPsbtPayload, BitcoinSignPsbtRawRequest, BitcoinSignPsbtRequest, BitcoinTransactionConfig, ChainStakingMetadata, ChainType, ConfirmationsQueue, CrowdloanItem, CrowdloanJson, CurrentAccountInfo, CurrentAccountProxyInfo, EvmProviderErrorType, EvmSendTransactionParams, EvmSendTransactionRequest, EvmSignatureRequest, ExternalRequestPromise, ExternalRequestPromiseStatus, ExtrinsicType, MantaAuthorizationContext, MantaPayConfig, MantaPaySyncState, NftCollection, NftItem, NftJson, NominatorMetadata, PsbtTransactionArg, RequestAccountExportPrivateKey, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestCrowdloanContributions, RequestSettingsType, ResponseAccountExportPrivateKey, ResponseCheckPublicAndSecretKey, ServiceInfo, SignMessageBitcoinResult, SignPsbtBitcoinResult, SingleModeJson, StakingItem, StakingJson, StakingRewardItem, StakingRewardJson, StakingType, UiSettings } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning } from '@subwallet/extension-base/background/types'; import { ALL_ACCOUNT_KEY, ALL_GENESIS_HASH, MANTA_PAY_BALANCE_INTERVAL } from '@subwallet/extension-base/constants'; import { NftService } from '@subwallet/extension-base/koni/api/nft'; @@ -1236,7 +1236,7 @@ export default class KoniState { }); } - public async bitcoinSignPspt (id: string, url: string, method: string, params: BitcoinSignPsbtRawRequest, allowedAccounts: string[]): Promise { + public async bitcoinSignPspt (id: string, url: string, networkKey: string, method: string, params: BitcoinSignPsbtRawRequest, allowedAccounts: string[]): Promise { const { account: address, allowedSighash, broadcast, network, psbt, signAtIndex } = params; if (!psbt || !address) { @@ -1281,8 +1281,32 @@ export default class KoniState { const psbtGenerate = bitcoin.Psbt.fromHex(psbt, { network: network === 'testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin }); - const psbtTxInputs = psbtGenerate.txInputs; - const psbtTxOutputs = psbtGenerate.txOutputs; + + const network_ = network === 'testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + + const tokenInfo = this.getNativeTokenInfo(networkKey); + + const psbtInputData = psbtGenerate.data.inputs.map(({ witnessUtxo }) => { + if (!witnessUtxo) { + return {}; + } + + const address = bitcoin.address.fromOutputScript(witnessUtxo?.script, network_); + + return { + address, + amount: witnessUtxo.value.toString() + } as PsbtTransactionArg; + }); + + const psbtOutputData = psbtGenerate.txOutputs.map((output) => { + const address = output.address || bitcoin.address.fromOutputScript(output.script, network_); + + return { + address, + amount: output.value.toString() + } as PsbtTransactionArg; + }); const payload: BitcoinSignPsbtPayload = { psbt: psbtGenerate, @@ -1291,8 +1315,9 @@ export default class KoniState { signAtIndex: isArray(signAtIndex) && signAtIndex.length === 0 ? undefined : signAtIndex, account: account.address, allowedSighash, - txInput: psbtTxInputs, - txOutput: psbtTxOutputs + tokenSlug: tokenInfo.slug, + txInput: psbtInputData, + txOutput: psbtOutputData }; const hashPayload = ''; const canSign = !account.isExternal; diff --git a/packages/extension-base/src/koni/background/handlers/Tabs.ts b/packages/extension-base/src/koni/background/handlers/Tabs.ts index e6cbdb6c403..d05d02fae46 100644 --- a/packages/extension-base/src/koni/background/handlers/Tabs.ts +++ b/packages/extension-base/src/koni/background/handlers/Tabs.ts @@ -963,8 +963,8 @@ export default class KoniTabs { }); } - public async canUseAccount (address: string, url: string) { - const allowedAccounts = await this.getEvmCurrentAccount(url); + public async canUseAccount (address: string, url: string, type?: string) { + const allowedAccounts = await (type === 'bitcoin' ? this.getBitcoinCurrentAccount(url) : this.getEvmCurrentAccount(url)); return !!allowedAccounts.find((acc) => (acc.toLowerCase() === address.toLowerCase())); } @@ -1240,8 +1240,15 @@ export default class KoniTabs { private async bitcoinSignPspt (id: string, url: string, { method, params }: RequestArguments) { const allowedAccounts = (await this.getBitcoinCurrentAccount(url)); + const psbtParams = params as BitcoinSignPsbtRawRequest; + const bitcoinState = await this.getBitcoinState(url, psbtParams.network); + const networkKey = bitcoinState.networkKey; + + if (!networkKey) { + throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Network unavailable. Please switch network or manually add network to wallet')); + } - const signResult = await this.#koniState.bitcoinSignPspt(id, url, method, params as BitcoinSignPsbtRawRequest, allowedAccounts); + const signResult = await this.#koniState.bitcoinSignPspt(id, url, networkKey, method, psbtParams, allowedAccounts); if (signResult) { return signResult; @@ -1252,7 +1259,7 @@ export default class KoniTabs { private async bitcoinSendTransfer (id: string, url: string, { params }: RequestArguments) { const transactionParams = params as BitcoinSendTransactionParams; - const canUseAccount = transactionParams.account && this.canUseAccount(transactionParams.account, url); + const canUseAccount = transactionParams.account && this.canUseAccount(transactionParams.account, url, 'bitcoin'); const bitcoinState = await this.getBitcoinState(url, transactionParams.network); const networkKey = bitcoinState.networkKey; diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx index ba6d8176b77..ea4f8f6b360 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx @@ -1,15 +1,18 @@ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { BitcoinSignPsbtRequest, ConfirmationsQueueItem } from '@subwallet/extension-base/background/KoniTypes'; +import { _ChainAsset } from '@subwallet/chain-list/types'; +import { BitcoinSignPsbtRequest, ConfirmationsQueueItem, PsbtTransactionArg } from '@subwallet/extension-base/background/KoniTypes'; import { AccountItemWithName, ConfirmationGeneralInfo, MetaInfo, ViewDetailIcon } from '@subwallet/extension-koni-ui/components'; import { useOpenDetailModal } from '@subwallet/extension-koni-ui/hooks'; import { BitcoinSignArea } from '@subwallet/extension-koni-ui/Popup/Confirmations/parts'; +import { RootState } from '@subwallet/extension-koni-ui/stores'; import { BitcoinSignatureSupportType, ThemeProps } from '@subwallet/extension-koni-ui/types'; -import { Button } from '@subwallet/react-ui'; +import { Button, Number } from '@subwallet/react-ui'; import CN from 'classnames'; -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { BaseDetailModal } from '../parts'; @@ -23,9 +26,36 @@ function Component ({ className, request, type }: Props) { const { id, payload } = request; const { t } = useTranslation(); const { account } = payload; - + const { tokenSlug, txInput, txOutput } = request.payload.payload; + const assetRegistry = useSelector((root: RootState) => root.assetRegistry.assetRegistry); const onClickDetail = useOpenDetailModal(); + const assetInfo: _ChainAsset | undefined = useMemo(() => { + return assetRegistry[tokenSlug]; + }, [assetRegistry, tokenSlug]); + const renderAccount = useCallback((accounts: PsbtTransactionArg[]) => { + return ( + <> + { + accounts.map(({ address, amount }) => + + : <>} + /> + ) + } + + + ); + }, [assetInfo.decimals, assetInfo.symbol]); + return ( <>
@@ -66,11 +96,12 @@ function Component ({ className, request, type }: Props) { > - {JSON.stringify(request.payload.payload.txInput)} + {renderAccount(txInput)} - {JSON.stringify(request.payload.payload.txOutput)} + {renderAccount(txOutput)} + From fa2a726819f6f4ad38ae0864dc4ed797cdbe4af5 Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Tue, 2 Jul 2024 19:27:34 +0700 Subject: [PATCH 02/11] [Issue 228] Update transaction time for sendTransfer request --- .../src/koni/background/handlers/Tabs.ts | 6 ++++-- .../src/services/transaction-service/index.ts | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/extension-base/src/koni/background/handlers/Tabs.ts b/packages/extension-base/src/koni/background/handlers/Tabs.ts index d05d02fae46..4b079957e60 100644 --- a/packages/extension-base/src/koni/background/handlers/Tabs.ts +++ b/packages/extension-base/src/koni/background/handlers/Tabs.ts @@ -982,7 +982,7 @@ export default class KoniTabs { public async evmSendTransaction (id: string, url: string, { params }: RequestArguments) { const transactionParams = (params as EvmSendTransactionParams[])[0]; - const canUseAccount = transactionParams.from && this.canUseAccount(transactionParams.from, url); + const canUseAccount = !!transactionParams.from && await this.canUseAccount(transactionParams.from, url); const evmState = await this.getEvmState(url); const networkKey = evmState.networkKey; @@ -1259,10 +1259,12 @@ export default class KoniTabs { private async bitcoinSendTransfer (id: string, url: string, { params }: RequestArguments) { const transactionParams = params as BitcoinSendTransactionParams; - const canUseAccount = transactionParams.account && this.canUseAccount(transactionParams.account, url, 'bitcoin'); + const canUseAccount = !!transactionParams.account && await this.canUseAccount(transactionParams.account, url, 'bitcoin'); const bitcoinState = await this.getBitcoinState(url, transactionParams.network); const networkKey = bitcoinState.networkKey; + console.log(canUseAccount, 'canUse'); + if (!canUseAccount) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('You have rescinded allowance for this account in wallet')); } diff --git a/packages/extension-base/src/services/transaction-service/index.ts b/packages/extension-base/src/services/transaction-service/index.ts index e64a8764f14..f4b6906c1b1 100644 --- a/packages/extension-base/src/services/transaction-service/index.ts +++ b/packages/extension-base/src/services/transaction-service/index.ts @@ -397,17 +397,34 @@ export default class TransactionService { emitter.on('success', (data: TransactionEventResponse) => { validatedTransaction.id = data.id; validatedTransaction.extrinsicHash = data.extrinsicHash; + this.handlePostProcessing(data.id); + this.onSuccess(data); }); emitter.on('signed', (data: TransactionEventResponse) => { validatedTransaction.id = data.id; validatedTransaction.extrinsicHash = data.extrinsicHash; + this.onSigned(data); }); emitter.on('error', (data: TransactionEventResponse) => { if (data.errors.length > 0) { validatedTransaction.errors.push(...data.errors); } + + this.onFailed({ ...data, errors: [...data.errors, new TransactionError(BasicTxErrorType.INTERNAL_ERROR)] }); + }); + + emitter.on('send', (data: TransactionEventResponse) => { + this.onSend(data); + }); + + emitter.on('extrinsicHash', (data: TransactionEventResponse) => { + this.onHasTransactionHash(data); + }); + + emitter.on('timeout', (data: TransactionEventResponse) => { + this.onTimeOut({ ...data, errors: [...data.errors, new TransactionError(BasicTxErrorType.TIMEOUT)] }); }); // @ts-ignore From ed914318bf46e553abf116ef2b4eb180a0274011 Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Wed, 3 Jul 2024 10:10:01 +0700 Subject: [PATCH 03/11] [Issue 228] [fix] Handle index account in case create with new seed phrase --- .../src/hooks/account/useGetDefaultAccountProxyName.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/extension-koni-ui/src/hooks/account/useGetDefaultAccountProxyName.ts b/packages/extension-koni-ui/src/hooks/account/useGetDefaultAccountProxyName.ts index 28f5f73bfeb..9bf112c4769 100644 --- a/packages/extension-koni-ui/src/hooks/account/useGetDefaultAccountProxyName.ts +++ b/packages/extension-koni-ui/src/hooks/account/useGetDefaultAccountProxyName.ts @@ -10,9 +10,15 @@ const useGetDefaultAccountProxyName = () => { const accountProxies = useSelector((state: RootState) => state.accountState.accountProxies); return useMemo(() => { - const filtered = accountProxies.filter((ap) => !isAccountAll(ap.proxyId)); + let accountIndex = 0; + const filtered = accountProxies + .filter((ap) => { + accountIndex = Math.max(Number.parseInt(ap.name?.split(' ')[1] || '0'), accountIndex); - return `Account ${filtered.length + 1}`; + return !isAccountAll(ap.proxyId); + }); + + return `Account ${Math.max(filtered.length, accountIndex) + 1}`; }, [accountProxies]); }; From 7a788559b0838dd874761188e6f67fd735c97e2b Mon Sep 17 00:00:00 2001 From: lw Date: Wed, 3 Jul 2024 12:37:55 +0700 Subject: [PATCH 04/11] Fix UI issues --- .../parts/Detail/Evm/Transaction.tsx | 19 ++++++++++++------- packages/extension-koni/public/main.css | 7 +++++++ .../extension-koni/public/notification.html | 2 -- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/Evm/Transaction.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/Evm/Transaction.tsx index f5c12bc6ffd..b261bf305ee 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/Evm/Transaction.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/Evm/Transaction.tsx @@ -111,14 +111,19 @@ const Component: React.FC = (props: Props) => { ) : null } - + + + { (!request.isToContract || amount !== 0) && ( diff --git a/packages/extension-koni/public/main.css b/packages/extension-koni/public/main.css index 6e85e1467c7..640d1fedc0e 100644 --- a/packages/extension-koni/public/main.css +++ b/packages/extension-koni/public/main.css @@ -34,6 +34,13 @@ body { position: relative; } +@media (max-height: 600px) { + html { + align-items: flex-start; + overflow: hidden; + } +} + [tabindex='-1']:focus { outline: none; } diff --git a/packages/extension-koni/public/notification.html b/packages/extension-koni/public/notification.html index b4c52e458ed..f445017a51a 100644 --- a/packages/extension-koni/public/notification.html +++ b/packages/extension-koni/public/notification.html @@ -10,8 +10,6 @@ margin: 0 auto; height: 600px; width: 390px; - display: flex; - align-items: center; min-height: 600px; } From 8cf88f47327e1c5a82a73c33c2509fa0195b9f8b Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Wed, 3 Jul 2024 16:25:34 +0700 Subject: [PATCH 05/11] [Issue 228] [fix] Refactor the code for sendTransfer in the new workflow. --- .../src/background/KoniTypes.ts | 11 +- .../src/koni/background/handlers/Extension.ts | 157 +++++++++++++++++- .../src/koni/background/handlers/State.ts | 145 +++------------- .../src/koni/background/handlers/Tabs.ts | 18 +- .../src/services/transaction-service/index.ts | 2 +- ...coinSendTransactionRequestConfirmation.tsx | 44 ++--- .../src/messaging/transaction/transfer.ts | 4 + 7 files changed, 224 insertions(+), 157 deletions(-) diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index db53bb3340d..e615edbc5c0 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -13,7 +13,7 @@ import { _BitcoinApi, _ChainState, _EvmApi, _NetworkUpsertParams, _SubstrateApi, import { CrowdloanContributionsResponse } from '@subwallet/extension-base/services/subscan-service/types'; import { BitcoinTransactionData, SWTransactionResponse, SWTransactionResult } from '@subwallet/extension-base/services/transaction-service/types'; import { WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types'; -import { BalanceJson, BitcoinFeeDetail, BuyServiceInfo, BuyTokenInfo, EarningRewardHistoryItem, EarningRewardJson, EarningStatus, HandleYieldStepParams, LeavePoolAdditionalData, NominationPoolInfo, OptimalYieldPath, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseEarlyValidateYield, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, SubmitYieldStepData, TokenApproveData, UnlockDotTransactionNft, UnstakingStatus, UtxoResponseItem, ValidateYieldProcessParams, YieldPoolInfo, YieldPositionInfo, YieldValidationStatus } from '@subwallet/extension-base/types'; +import { BalanceJson, BuyServiceInfo, BuyTokenInfo, EarningRewardHistoryItem, EarningRewardJson, EarningStatus, HandleYieldStepParams, LeavePoolAdditionalData, NominationPoolInfo, OptimalYieldPath, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseEarlyValidateYield, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, SubmitYieldStepData, TokenApproveData, UnlockDotTransactionNft, UnstakingStatus, ValidateYieldProcessParams, YieldPoolInfo, YieldPositionInfo, YieldValidationStatus } from '@subwallet/extension-base/types'; import { InjectedAccount, InjectedAccountWithMeta, MetadataDefBase } from '@subwallet/extension-inject/types'; import { KeypairType, KeyringPair$Json, KeyringPair$Meta } from '@subwallet/keyring/types'; import { KeyringOptions } from '@subwallet/ui-keyring/options/types'; @@ -1397,10 +1397,7 @@ export interface EvmSendTransactionRequest extends TransactionConfig, EvmSignReq isToContract: boolean; } -export interface BitcoinSendTransactionRequest extends BitcoinSignRequest, BitcoinTransactionConfig { - outputs?: BitcoinOutputUtox[], - inputs?: UtxoResponseItem[], -} +export interface BitcoinSendTransactionRequest extends BitcoinSignRequest, BitcoinTransactionConfig {} export type EvmWatchTransactionRequest = EvmSendTransactionRequest; export type BitcoinWatchTransactionRequest = BitcoinSendTransactionRequest; @@ -1422,11 +1419,10 @@ export interface BitcoinOutputUtox { export interface BitcoinTransactionConfig{ id?: string, from?: string | number; - to?: string; + to?: BitcoinRecipientTransactionParams[]; value?: number | string | BN; networkKey?: string; tokenSlug?: string; - fee?: BitcoinFeeDetail; } export interface SignMessageBitcoinResult { @@ -2569,6 +2565,7 @@ export interface KoniRequestSignatures { 'pri(transfer.getExistentialDeposit)': [RequestTransferExistentialDeposit, string]; 'pri(transfer.getMaxTransferable)': [RequestMaxTransferable, AmountData]; 'pri(transfer.subscribe)': [RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransfer]; + 'pri(transfer.confirmation.subscribe)': [RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransfer]; 'pri(subscription.cancel)': [string, boolean]; 'pri(freeBalance.get)': [RequestFreeBalance, AmountData]; 'pri(freeBalance.subscribe)': [RequestFreeBalance, AmountDataWithId, AmountDataWithId]; diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 49cd1f61054..ca595f992af 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -4,10 +4,11 @@ import { Common } from '@ethereumjs/common'; import { LegacyTransaction } from '@ethereumjs/tx'; import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from '@subwallet/chain-list/types'; +import { BitcoinProviderError } from '@subwallet/extension-base/background/errors/BitcoinProviderError'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { withErrorLog } from '@subwallet/extension-base/background/handlers/helpers'; import { createSubscription } from '@subwallet/extension-base/background/handlers/subscriptions'; -import { AccountExternalError, AccountExternalErrorCode, AccountProxiesWithCurrentProxy, AccountsWithCurrentAddress, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BasicTxErrorType, BasicTxWarningCode, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CreateDeriveAccountInfo, CronReloadRequest, CrowdloanJson, CurrentAccountInfo, DeriveAccountInfo, ExternalRequestPromiseStatus, ExtrinsicType, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, OptionInputAddress, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateSuriV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAccountMeta, RequestAccountProxyCreateSuri, RequestAccountProxyEdit, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerAccountProxy, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBatchRestoreV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConnectWalletConnect, RequestCrossChainTransfer, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDeriveCreateMultiple, RequestDeriveCreateV2, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetDeriveAccounts, RequestGetTransaction, RequestJsonRestoreV2, RequestKeyringExportAccountProxyMnemonic, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveRecentAccount, RequestSeedCreateV2, RequestSeedValidateV2, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateSuriV2, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseAccountMeta, ResponseChangeMasterPassword, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseFindRawMetadata, ResponseGetDeriveAccounts, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponsePrivateKeyValidateV2, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSeedCreateV2, ResponseSeedValidateV2, ResponseSubscribeHistory, ResponseUnlockKeyring, StakingJson, StakingRewardJson, StakingTxErrorType, StakingType, SupportTransferResponse, ThemeNames, TransactionHistoryItem, TransactionResponse, TransferTxErrorType, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountExternalError, AccountExternalErrorCode, AccountProxiesWithCurrentProxy, AccountsWithCurrentAddress, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BasicTxErrorType, BasicTxWarningCode, BitcoinProviderErrorType, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CreateDeriveAccountInfo, CronReloadRequest, CrowdloanJson, CurrentAccountInfo, DeriveAccountInfo, ExternalRequestPromiseStatus, ExtrinsicType, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, OptionInputAddress, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateSuriV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAccountMeta, RequestAccountProxyCreateSuri, RequestAccountProxyEdit, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerAccountProxy, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBatchRestoreV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConnectWalletConnect, RequestCrossChainTransfer, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDeriveCreateMultiple, RequestDeriveCreateV2, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetDeriveAccounts, RequestGetTransaction, RequestJsonRestoreV2, RequestKeyringExportAccountProxyMnemonic, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveRecentAccount, RequestSeedCreateV2, RequestSeedValidateV2, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateSuriV2, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseAccountMeta, ResponseChangeMasterPassword, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseFindRawMetadata, ResponseGetDeriveAccounts, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponsePrivateKeyValidateV2, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSeedCreateV2, ResponseSeedValidateV2, ResponseSubscribeHistory, ResponseUnlockKeyring, StakingJson, StakingRewardJson, StakingTxErrorType, StakingType, SupportTransferResponse, ThemeNames, TransactionHistoryItem, TransactionResponse, TransferTxErrorType, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson, AccountProxy, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountProxy, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeCancel, RequestAuthorizeReject, RequestBatchRestore, RequestCurrentAccountAddress, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApproveSignature, RequestSigningCancel, RequestTypes, ResponseAccountExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseType, SigningRequest, WindowOpenParams } from '@subwallet/extension-base/background/types'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { ALL_ACCOUNT_KEY, ALL_GENESIS_HASH, BTC_DUST_AMOUNT, SUPPORT_KEYPAIR_TYPES, XCM_FEE_RATIO, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants'; @@ -39,7 +40,7 @@ import { WALLET_CONNECT_EIP155_NAMESPACE } from '@subwallet/extension-base/servi import { isProposalExpired, isSupportWalletConnectChain, isSupportWalletConnectNamespace } from '@subwallet/extension-base/services/wallet-connect-service/helpers'; import { ResultApproveWalletConnectSession, WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types'; import { AccountsStore } from '@subwallet/extension-base/stores'; -import { BalanceJson, BitcoinFeeInfo, BitcoinFeeRate, BuyServiceInfo, BuyTokenInfo, DetermineUtxosForSpendArgs, EarningRewardJson, EvmEIP1995FeeOption, EvmFeeInfo, FeeChainType, FeeDetail, FeeInfo, GetFeeFunction, NominationPoolInfo, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, SubstrateFeeInfo, ValidateYieldProcessParams, YieldPoolType } from '@subwallet/extension-base/types'; +import { BalanceJson, BitcoinFeeDetail, BitcoinFeeInfo, BitcoinFeeRate, BuyServiceInfo, BuyTokenInfo, DetermineUtxosForSpendArgs, EarningRewardJson, EvmEIP1995FeeOption, EvmFeeInfo, FeeChainType, FeeDetail, FeeInfo, GetFeeFunction, NominationPoolInfo, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, SubstrateFeeInfo, ValidateYieldProcessParams, YieldPoolType } from '@subwallet/extension-base/types'; import { combineBitcoinFee, combineEthFee, convertSubjectInfoToAddresses, createTransactionFromRLP, determineUtxosForSpend, determineUtxosForSpendAll, filterUneconomicalUtxos, generateAccountProxyId, getSizeInfo, isAddressValidWithAuthType, isSameAddress, keyringGetAccounts, reformatAddress, signatureToHex, Transaction as QrTransaction, uniqueStringArray } from '@subwallet/extension-base/utils'; import { parseContractInput, parseEvmRlp } from '@subwallet/extension-base/utils/eth/parseTransaction'; import { balanceFormatter, BN_ZERO, formatNumber } from '@subwallet/extension-base/utils/number'; @@ -2805,6 +2806,156 @@ export default class KoniExtension { return convertData(freeBalance, fee); } + private async subscribeTransferableWhenConfirmation ({ address, chain, feeCustom, feeOption: _feeOptions, to, token, transferAll, value }: RequestSubscribeTransfer, id: string, port: chrome.runtime.Port): Promise { + const cb = createSubscription<'pri(transfer.confirmation.subscribe)'>(id, port); + + const tokenInfo = token ? this.#koniState.chainService.getAssetBySlug(token) : this.#koniState.chainService.getNativeTokenInfo(chain); + const freeBalanceSubject = new Subject(); + const feeSubject = new Subject(); + const feeType: FeeChainType = 'bitcoin'; + + const convertData = async (freeBalance: AmountData, fee: BitcoinFeeInfo): Promise => { + let estimatedFee = '0'; + let feeOptions: BitcoinFeeDetail | null = null; + const amount = parseInt(value || '0'); + let maxTransferable = new BigN(freeBalance.value); + + try { + const _fee = fee; + const _feeCustom = feeCustom as BitcoinFeeRate; + const combineFee = combineBitcoinFee(_fee, _feeOptions, _feeCustom); + const bitcoinApi = this.#koniState.chainService.getBitcoinApi(chain); + const utxos = await getTransferableBitcoinUtxos(bitcoinApi, address); + const determineUtxosArgs: DetermineUtxosForSpendArgs = { + amount, + feeRate: combineFee.feeRate, + recipient: to || address, + sender: address, + utxos + }; + + const recipients = transferAll ? [address] : [address, to || address]; + + const fallbackCalculate = (recipients: string[]) => { + const utxos = filterUneconomicalUtxos({ + utxos: determineUtxosArgs.utxos, + feeRate: determineUtxosArgs.feeRate, + recipients, + sender: determineUtxosArgs.sender + }); + + const { txVBytes: vSize } = getSizeInfo({ + inputLength: utxos.length || 1, + sender: address, + recipients + }); + + return { + vSize, + maxTransferable: utxos.reduce((previous, input) => previous.plus(input.value), new BigN(0)), + estimatedFee: Math.ceil(determineUtxosArgs.feeRate * vSize).toString() + }; + }; + + try { + const { fee: _estimatedFee, inputs } = transferAll ? determineUtxosForSpendAll(determineUtxosArgs) : determineUtxosForSpend(determineUtxosArgs); + + maxTransferable = inputs.reduce((previous, input) => previous.plus(input.value), new BigN(0)); + + const { txVBytes: vSize } = getSizeInfo({ + inputLength: inputs.length, + sender: address, + recipients + }); + + estimatedFee = new BigN(_estimatedFee).toFixed(0); + feeOptions = { + ..._fee, + estimatedFee, + vSize + }; + } catch (_e) { + const fb = fallbackCalculate([to || address]); + + maxTransferable = fb.maxTransferable; + + if (!feeOptions) { + const fb = fallbackCalculate([address, to || address]); + + estimatedFee = fb.estimatedFee; + + feeOptions = { + ..._fee, + estimatedFee, + vSize: fb.vSize + }; + } + } + } catch (e) { + feeOptions = { + ...fee, + estimatedFee, + vSize: 0 + }; + + console.warn('Unable to estimate fee', e); + } + + if (maxTransferable.lt(new BigN(estimatedFee).plus(new BigN(amount)))) { + throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Insufficient balance')); + } + + maxTransferable = maxTransferable + .minus(new BigN(estimatedFee)); + + return { + maxTransferable: !_isNativeToken(tokenInfo) + ? freeBalance.value + : maxTransferable.gt(BN_ZERO) ? (maxTransferable.toFixed(0) || '0') : '0', + feeOptions: feeOptions as FeeDetail, + feeType, + id + }; + }; + + const subscription = combineLatest({ + freeBalance: freeBalanceSubject, + fee: feeSubject + }) + .subscribe({ + next: ({ fee, freeBalance }) => { + convertData(freeBalance, fee) + .then(cb) + .catch(console.error); + } + }); + + const [unsubBalance, freeBalance] = await this.#koniState.balanceService.subscribeTokenFreeBalance(address, chain, token, (data) => { + freeBalanceSubject.next(data); // Must be called after subscription + }); + + const fee = await this.#koniState.feeService.subscribeChainFee(id, chain, feeType, (data) => { + feeSubject.next(data as BitcoinFeeInfo); // Must be called after subscription + }); + + const unsub = () => { + subscription.unsubscribe(); + unsubBalance(); + this.#koniState.feeService.unsubscribeChainFee(id, chain, feeType); + }; + + this.createUnsubscriptionHandle( + id, + unsub + ); + + port.onDisconnect.addListener((): void => { + this.cancelSubscription(id); + }); + + return convertData(freeBalance, fee as BitcoinFeeInfo); + } + private async subscribeAddressFreeBalance ({ address, networkKey, token }: RequestFreeBalance, id: string, port: chrome.runtime.Port): Promise { const cb = createSubscription<'pri(freeBalance.subscribe)'>(id, port); @@ -5488,6 +5639,8 @@ export default class KoniExtension { return this.transferGetMaxTransferable(request as RequestMaxTransferable); case 'pri(transfer.subscribe)': return this.subscribeMaxTransferable(request as RequestSubscribeTransfer, id, port); + case 'pri(transfer.confirmation.subscribe)': + return this.subscribeTransferableWhenConfirmation(request as RequestSubscribeTransfer, id, port); case 'pri(freeBalance.get)': return this.getAddressFreeBalance(request as RequestFreeBalance); case 'pri(freeBalance.subscribe)': diff --git a/packages/extension-base/src/koni/background/handlers/State.ts b/packages/extension-base/src/koni/background/handlers/State.ts index 7f66ac4c460..2dc07b2ab61 100644 --- a/packages/extension-base/src/koni/background/handlers/State.ts +++ b/packages/extension-base/src/koni/background/handlers/State.ts @@ -6,12 +6,11 @@ import { BitcoinProviderError } from '@subwallet/extension-base/background/error import { EvmProviderError } from '@subwallet/extension-base/background/errors/EvmProviderError'; import { withErrorLog } from '@subwallet/extension-base/background/handlers/helpers'; import { isSubscriptionRunning, unsubscribe } from '@subwallet/extension-base/background/handlers/subscriptions'; -import { AccountRefMap, AddTokenRequestExternal, AmountData, APIItemState, ApiMap, AuthRequestV2, BasicTxErrorType, BitcoinOutputUtox, BitcoinProviderErrorType, BitcoinSendTransactionParams, BitcoinSendTransactionRequest, BitcoinSignatureRequest, BitcoinSignPsbtPayload, BitcoinSignPsbtRawRequest, BitcoinSignPsbtRequest, BitcoinTransactionConfig, ChainStakingMetadata, ChainType, ConfirmationsQueue, CrowdloanItem, CrowdloanJson, CurrentAccountInfo, CurrentAccountProxyInfo, EvmProviderErrorType, EvmSendTransactionParams, EvmSendTransactionRequest, EvmSignatureRequest, ExternalRequestPromise, ExternalRequestPromiseStatus, ExtrinsicType, MantaAuthorizationContext, MantaPayConfig, MantaPaySyncState, NftCollection, NftItem, NftJson, NominatorMetadata, PsbtTransactionArg, RequestAccountExportPrivateKey, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestCrowdloanContributions, RequestSettingsType, ResponseAccountExportPrivateKey, ResponseCheckPublicAndSecretKey, ServiceInfo, SignMessageBitcoinResult, SignPsbtBitcoinResult, SingleModeJson, StakingItem, StakingJson, StakingRewardItem, StakingRewardJson, StakingType, UiSettings } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountRefMap, AddTokenRequestExternal, AmountData, APIItemState, ApiMap, AuthRequestV2, BasicTxErrorType, BitcoinProviderErrorType, BitcoinSendTransactionParams, BitcoinSendTransactionRequest, BitcoinSignatureRequest, BitcoinSignPsbtPayload, BitcoinSignPsbtRawRequest, BitcoinSignPsbtRequest, BitcoinTransactionConfig, ChainStakingMetadata, ChainType, ConfirmationsQueue, CrowdloanItem, CrowdloanJson, CurrentAccountInfo, CurrentAccountProxyInfo, EvmProviderErrorType, EvmSendTransactionParams, EvmSendTransactionRequest, EvmSignatureRequest, ExternalRequestPromise, ExternalRequestPromiseStatus, ExtrinsicType, MantaAuthorizationContext, MantaPayConfig, MantaPaySyncState, NftCollection, NftItem, NftJson, NominatorMetadata, PsbtTransactionArg, RequestAccountExportPrivateKey, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestCrowdloanContributions, RequestSettingsType, ResponseAccountExportPrivateKey, ResponseCheckPublicAndSecretKey, ServiceInfo, SignMessageBitcoinResult, SignPsbtBitcoinResult, SingleModeJson, StakingItem, StakingJson, StakingRewardItem, StakingRewardJson, StakingType, UiSettings } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning } from '@subwallet/extension-base/background/types'; import { ALL_ACCOUNT_KEY, ALL_GENESIS_HASH, MANTA_PAY_BALANCE_INTERVAL } from '@subwallet/extension-base/constants'; import { NftService } from '@subwallet/extension-base/koni/api/nft'; import { BalanceService } from '@subwallet/extension-base/services/balance-service'; -import { getTransferableBitcoinUtxos } from '@subwallet/extension-base/services/balance-service/helpers/balance/bitcoin'; import { ServiceStatus } from '@subwallet/extension-base/services/base/types'; import BuyService from '@subwallet/extension-base/services/buy-service'; import CampaignService from '@subwallet/extension-base/services/campaign-service'; @@ -40,8 +39,8 @@ import { TransactionEventResponse } from '@subwallet/extension-base/services/tra import WalletConnectService from '@subwallet/extension-base/services/wallet-connect-service'; import { SWStorage } from '@subwallet/extension-base/storage'; import AccountRefStore from '@subwallet/extension-base/stores/AccountRef'; -import { BalanceItem, BalanceMap, DetermineUtxosForSpendArgs, EvmFeeInfo, UtxoResponseItem } from '@subwallet/extension-base/types'; -import { determineUtxosForSpend, filterUneconomicalUtxos, getSizeInfo, isAccountAll, keyringGetAccounts, stripUrl, targetIsWeb } from '@subwallet/extension-base/utils'; +import { BalanceItem, BalanceMap, EvmFeeInfo } from '@subwallet/extension-base/types'; +import { isAccountAll, keyringGetAccounts, stripUrl, targetIsWeb } from '@subwallet/extension-base/utils'; import { isContractAddress, parseContractInput } from '@subwallet/extension-base/utils/eth/parseTransaction'; import { createPromiseHandler } from '@subwallet/extension-base/utils/promise'; import { MetadataDef, ProviderMeta } from '@subwallet/extension-inject/types'; @@ -1247,10 +1246,6 @@ export default class KoniState { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Psbt to be signed must be hex-encoded')); } - if (!(network === 'mainnet' || network === 'testnet')) { - throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Network to try this request is must be mainnet or testnet')); - } - if (!isBitcoinAddress(address)) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Not found address')); } @@ -1266,11 +1261,11 @@ export default class KoniState { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Unable to find account')); } - if (network === 'mainnet') { + if (networkKey === 'bitcoin') { if (!['bitcoin-86', 'bitcoin-84'].includes(pair.type)) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Your address is not on the mainnet network')); } - } else if (network === 'testnet') { + } else if (networkKey === 'bitcoinTestnet') { if (!['bittest-86', 'bittest-84'].includes(pair.type)) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Your address is not on the testnet network')); } @@ -1278,12 +1273,12 @@ export default class KoniState { const account: AccountJson = { address: pair.address, ...pair.meta }; + const network_ = networkKey === 'bitcoinTestnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + const psbtGenerate = bitcoin.Psbt.fromHex(psbt, { - network: network === 'testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + network: network_ }); - const network_ = network === 'testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; - const tokenInfo = this.getNativeTokenInfo(networkKey); const psbtInputData = psbtGenerate.data.inputs.map(({ witnessUtxo }) => { @@ -1346,10 +1341,7 @@ export default class KoniState { } public async bitcoinSendTransaction (id: string, url: string, networkKey: string, allowedAccounts: string[], transactionParams: BitcoinSendTransactionParams): Promise { - const bitcoinApi = this.getBitcoinApi(networkKey); - const apiStrategy = bitcoinApi.api; - - const autoFormatNumber = (val?: string | number): string | undefined => { + const autoFormatNumber = (val: string | number): string => { if (typeof val === 'string' && val.startsWith('0x')) { return new BigN(val.replace('0x', ''), 16).toString(); } else if (typeof val === 'number') { @@ -1368,14 +1360,24 @@ export default class KoniState { } const tokenInfo = this.getNativeTokenInfo(networkKey); + let totalValue = new BigN('0'); + const to = transactionParams.recipients.map((value) => { + const amount = autoFormatNumber(value.amount); + totalValue = totalValue.plus(amount); + + return { + ...value, + amount + }; + }); const transaction: BitcoinTransactionConfig = { id, from: transactionParams.account, - to: transactionParams.recipients[0].address, - value: autoFormatNumber(transactionParams.recipients[0].amount), + to, + value: totalValue.toString(), tokenSlug: tokenInfo.slug, - networkKey: transactionParams.network === 'testnet' ? 'bitcoinTestnet' : 'bitcoin' + networkKey }; // Address is validated in before step @@ -1391,11 +1393,11 @@ export default class KoniState { throw new EvmProviderError(EvmProviderErrorType.INVALID_PARAMS, t('Unable to find account')); } - if (networkKey === 'mainnet') { + if (networkKey === 'bitcoin') { if (!['bitcoin-86', 'bitcoin-84'].includes(pair.type)) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Your address is not on the mainnet network')); } - } else if (networkKey === 'testnet') { + } else if (networkKey === 'bitcoinTestnet') { if (!['bittest-86', 'bittest-84'].includes(pair.type)) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Your address is not on the testnet network')); } @@ -1403,109 +1405,10 @@ export default class KoniState { const account: AccountJson = { address: pair.address, ...pair.meta }; - // Calculate transaction data - const [feeOptions_, utxos] = await Promise.all([ - apiStrategy.getRecommendedFeeRate(), - getTransferableBitcoinUtxos(bitcoinApi, fromAddress) - ]); - - const optionDefault = feeOptions_.options.default; - let feeOptions = null; - const determineUtxosArgs: DetermineUtxosForSpendArgs = { - amount: parseInt(transaction.value as string || '0'), - feeRate: feeOptions_.options[optionDefault].feeRate, - recipient: transaction.to as string, - sender: account.address, - utxos - }; - - if (!transaction.to) { - throw new Error(); - } - - const fallbackCalculate = (recipients: string[]) => { - const utxos = filterUneconomicalUtxos({ - utxos: determineUtxosArgs.utxos, - feeRate: determineUtxosArgs.feeRate, - recipients, - sender: determineUtxosArgs.sender - }); - - const { txVBytes: vSize } = getSizeInfo({ - inputLength: utxos.length || 1, - sender: fromAddress, - recipients - }); - - return { - vSize, - maxTransferable: utxos.reduce((previous, input) => previous.plus(input.value), new BigN(0)), - estimatedFee: Math.ceil(determineUtxosArgs.feeRate * vSize).toString() - }; - }; - - const getBalance = async (senderAddress: string) => { - const filteredUtxos = await getTransferableBitcoinUtxos(bitcoinApi, senderAddress); - - let balanceValue = new BigN(0); - - filteredUtxos.forEach((utxo) => { - balanceValue = balanceValue.plus(utxo.value); - }); - - return balanceValue; - }; - - let maxTransferable = new BigN('0'); - let estimatedFee = '0'; - let inputs: UtxoResponseItem[] = []; - let outputs: BitcoinOutputUtox[] = []; - - try { - const { fee: _estimatedFee, inputs: inputsRs, outputs: outputRs } = determineUtxosForSpend(determineUtxosArgs); - - const { txVBytes: vSize } = getSizeInfo({ - inputLength: inputs.length, - sender: fromAddress, - recipients: [transaction.to] - }); - - inputs = [...inputsRs]; - outputs = [...outputRs]; - estimatedFee = new BigN(_estimatedFee).toFixed(0); - feeOptions = { - ...feeOptions_, - estimatedFee, - vSize - }; - } catch (_e) { - if (!feeOptions) { - const fb = fallbackCalculate([transaction.to, transaction.to]); - - estimatedFee = fb.estimatedFee; - - feeOptions = { - ...feeOptions_, - estimatedFee, - vSize: fb.vSize - }; - } - } - - maxTransferable = await getBalance(fromAddress); - - // Validate balance - if (maxTransferable.lt(new BigN(estimatedFee).plus(new BigN(autoFormatNumber(transactionParams.recipients[0].amount) || '0')))) { - throw new EvmProviderError(EvmProviderErrorType.INVALID_PARAMS, t('Insufficient balance')); - } - const requestPayload: BitcoinSendTransactionRequest = { ...transaction, hashPayload: JSON.stringify(transaction), - fee: feeOptions, - inputs, canSign: true, - outputs, account: account }; diff --git a/packages/extension-base/src/koni/background/handlers/Tabs.ts b/packages/extension-base/src/koni/background/handlers/Tabs.ts index 4b079957e60..a11c3e750ee 100644 --- a/packages/extension-base/src/koni/background/handlers/Tabs.ts +++ b/packages/extension-base/src/koni/background/handlers/Tabs.ts @@ -1241,7 +1241,13 @@ export default class KoniTabs { private async bitcoinSignPspt (id: string, url: string, { method, params }: RequestArguments) { const allowedAccounts = (await this.getBitcoinCurrentAccount(url)); const psbtParams = params as BitcoinSignPsbtRawRequest; + + if (!(psbtParams.network === 'mainnet' || psbtParams.network === 'testnet')) { + throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Network to try this request is must be mainnet or testnet')); + } + const bitcoinState = await this.getBitcoinState(url, psbtParams.network); + const networkKey = bitcoinState.networkKey; if (!networkKey) { @@ -1263,8 +1269,6 @@ export default class KoniTabs { const bitcoinState = await this.getBitcoinState(url, transactionParams.network); const networkKey = bitcoinState.networkKey; - console.log(canUseAccount, 'canUse'); - if (!canUseAccount) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('You have rescinded allowance for this account in wallet')); } @@ -1275,7 +1279,7 @@ export default class KoniTabs { const senderAccountType = getKeypairTypeByAddress(transactionParams.account); - if ((transactionParams.network === 'mainnet' && senderAccountType !== 'bitcoin-84') || (transactionParams.network === 'testnet' && senderAccountType !== 'bittest-84')) { + if ((networkKey === 'bitcoin' && senderAccountType !== 'bitcoin-84') || (networkKey === 'bitcoinTestnet' && senderAccountType !== 'bittest-84')) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('The account or the network is incorrect')); } @@ -1291,14 +1295,18 @@ export default class KoniTabs { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t("We don't support multiple recipients yet. Please provide only one for now.")); } + if (transactionParams.recipients.filter(({ address, amount }) => !address || !amount).length > 0) { + throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS); + } + if (transactionParams.account === transactionParams.recipients[0].address) { - throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t("The recipient address cannot be the same as the sender's")); + throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t("The recipient address cannot be the same as the sender's address")); } const recipientAccountType = getKeypairTypeByAddress(transactionParams.recipients[0].address); if (senderAccountType !== recipientAccountType) { - throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t("The recipient address type must be the same as the sender's")); + throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t("The type of the recipient's address must match the type of the sender's address")); } const allowedAccounts = await this.getBitcoinCurrentAccount(url); diff --git a/packages/extension-base/src/services/transaction-service/index.ts b/packages/extension-base/src/services/transaction-service/index.ts index f4b6906c1b1..2638fddd59e 100644 --- a/packages/extension-base/src/services/transaction-service/index.ts +++ b/packages/extension-base/src/services/transaction-service/index.ts @@ -175,7 +175,7 @@ export default class TransactionService { if (!web3) { validationResponse.errors.push(new TransactionError(BasicTxErrorType.CHAIN_DISCONNECTED, undefined)); } else { - const gasLimit = await web3.api.eth.estimateGas(transaction); + const gasLimit = await web3.api.eth.estimateGas(transaction as TransactionConfig); const feeInfo = await this.state.feeService.subscribeChainFee(id, chain, 'evm') as EvmFeeInfo; const feeCombine = combineEthFee(feeInfo, feeOption, feeCustom as EvmEIP1995FeeOption); diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx index f5b6cdac360..acfa82ef088 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx @@ -7,8 +7,8 @@ import { BitcoinFeeDetail, RequestSubmitTransferWithId, ResponseSubscribeTransfe import { BN_ZERO, getDomainFromUrl } from '@subwallet/extension-base/utils'; import { BitcoinFeeSelector, MetaInfo } from '@subwallet/extension-koni-ui/components'; import { RenderFieldNodeParams } from '@subwallet/extension-koni-ui/components/Field/TransactionFee/BitcoinFeeSelector'; -import { useGetAccountByAddress } from '@subwallet/extension-koni-ui/hooks'; -import { cancelSubscription, subscribeMaxTransfer } from '@subwallet/extension-koni-ui/messaging'; +import { useGetAccountByAddress, useNotification } from '@subwallet/extension-koni-ui/hooks'; +import { cancelSubscription, subscribeTransferWhenConfirmation } from '@subwallet/extension-koni-ui/messaging'; import { BitcoinSignArea } from '@subwallet/extension-koni-ui/Popup/Confirmations/parts'; import { RootState } from '@subwallet/extension-koni-ui/stores'; import { BitcoinSignatureSupportType, ThemeProps } from '@subwallet/extension-koni-ui/types'; @@ -35,14 +35,19 @@ const convertToBigN = (num: BitcoinSendTransactionRequest['value']): string | nu }; function Component ({ className, request, type }: Props) { - const { id, payload: { account, fee, networkKey, to, tokenSlug, value } } = request; + const { id, payload: { account, networkKey, to, tokenSlug, value } } = request; const { t } = useTranslation(); + const transferAmountValue = useMemo(() => value?.toString() as string, [value]); + const fromValue = useMemo(() => account.address, [account.address]); + const toValue = useMemo(() => to ? to[0].address : '', [to]); + const chainValue = useMemo(() => networkKey as string, [networkKey]); + const assetValue = useMemo(() => tokenSlug as string, [tokenSlug]); const [transactionInfo, setTransactionInfo] = useState({ id, chain: networkKey as string, from: account.address, - to: to as string, + to: toValue, tokenSlug: tokenSlug as string, transferAll: false, value: value?.toString() || '0' @@ -50,18 +55,11 @@ function Component ({ className, request, type }: Props) { const [isFetchingInfo, setIsFetchingInfo] = useState(false); const [isTransferAll, setIsTransferAll] = useState(false); const [transferInfo, setTransferInfo] = useState(); - const [transactionFeeInfo, setTransactionFeeInfo] = useState({ - feeOption: fee?.options.default - }); - + const [transactionFeeInfo, setTransactionFeeInfo] = useState(undefined); + const [isErrorTransaction, setIsErrorTransaction] = useState(false); + const notify = useNotification(); const assetRegistry = useSelector((root: RootState) => root.assetRegistry.assetRegistry); - const transferAmountValue = value?.toString() as string; - const fromValue = account.address; - const toValue = to as string; - const chainValue = networkKey as string; - const assetValue = tokenSlug as string; - const assetInfo: _ChainAsset | undefined = useMemo(() => { return assetRegistry[assetValue]; }, [assetRegistry, assetValue]); @@ -143,11 +141,10 @@ function Component ({ className, request, type }: Props) { if (fromValue && assetValue) { timeout = setTimeout(() => { - subscribeMaxTransfer({ + subscribeTransferWhenConfirmation({ address: fromValue, chain: chainValue, token: assetValue, - isXcmTransfer: false, destChain: chainValue, feeOption: transactionFeeInfo?.feeOption, feeCustom: transactionFeeInfo?.feeCustom, @@ -158,7 +155,12 @@ function Component ({ className, request, type }: Props) { .then(callback) .catch((e) => { console.error(e); - + notify({ + message: t(e), + type: 'error', + duration: 8 + }); + setIsErrorTransaction(true); setTransferInfo(undefined); }) .finally(() => { @@ -172,7 +174,7 @@ function Component ({ className, request, type }: Props) { clearTimeout(timeout); id && cancelSubscription(id).catch(console.error); }; - }, [assetRegistry, assetValue, chainValue, fromValue, toValue, transactionFeeInfo, transferAmountValue, isTransferAll]); + }, [assetRegistry, assetValue, chainValue, fromValue, toValue, transactionFeeInfo, transferAmountValue, isTransferAll, notify, t]); return ( <> @@ -206,14 +208,14 @@ function Component ({ className, request, type }: Props) { value={amount} /> - + />} {/* {!!transaction.estimateFee?.tooHigh && ( */} @@ -226,7 +228,7 @@ function Component ({ className, request, type }: Props) { {/* )} */}
void): Promise { return sendMessage('pri(transfer.subscribe)', request, callback); } + +export async function subscribeTransferWhenConfirmation (request: RequestSubscribeTransfer, callback: (data: ResponseSubscribeTransfer) => void): Promise { + return sendMessage('pri(transfer.confirmation.subscribe)', request, callback); +} From 4904c495fbbe997fcd8a6a4b4f644c4bddcb176a Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Thu, 4 Jul 2024 11:58:56 +0700 Subject: [PATCH 06/11] [Issue 228] [fix] Refactor the code for sendTransfer in the new workflow #2 --- .../src/background/KoniTypes.ts | 4 +- .../src/koni/background/handlers/Extension.ts | 113 +++++++----------- .../src/types/balance/transfer.ts | 2 + ...coinSendTransactionRequestConfirmation.tsx | 22 +--- .../src/messaging/transaction/transfer.ts | 4 +- 5 files changed, 55 insertions(+), 90 deletions(-) diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index e615edbc5c0..daf7e42fcce 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -13,7 +13,7 @@ import { _BitcoinApi, _ChainState, _EvmApi, _NetworkUpsertParams, _SubstrateApi, import { CrowdloanContributionsResponse } from '@subwallet/extension-base/services/subscan-service/types'; import { BitcoinTransactionData, SWTransactionResponse, SWTransactionResult } from '@subwallet/extension-base/services/transaction-service/types'; import { WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types'; -import { BalanceJson, BuyServiceInfo, BuyTokenInfo, EarningRewardHistoryItem, EarningRewardJson, EarningStatus, HandleYieldStepParams, LeavePoolAdditionalData, NominationPoolInfo, OptimalYieldPath, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseEarlyValidateYield, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, SubmitYieldStepData, TokenApproveData, UnlockDotTransactionNft, UnstakingStatus, ValidateYieldProcessParams, YieldPoolInfo, YieldPositionInfo, YieldValidationStatus } from '@subwallet/extension-base/types'; +import { BalanceJson, BuyServiceInfo, BuyTokenInfo, EarningRewardHistoryItem, EarningRewardJson, EarningStatus, HandleYieldStepParams, LeavePoolAdditionalData, NominationPoolInfo, OptimalYieldPath, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseEarlyValidateYield, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation, SubmitYieldStepData, TokenApproveData, UnlockDotTransactionNft, UnstakingStatus, ValidateYieldProcessParams, YieldPoolInfo, YieldPositionInfo, YieldValidationStatus } from '@subwallet/extension-base/types'; import { InjectedAccount, InjectedAccountWithMeta, MetadataDefBase } from '@subwallet/extension-inject/types'; import { KeypairType, KeyringPair$Json, KeyringPair$Meta } from '@subwallet/keyring/types'; import { KeyringOptions } from '@subwallet/ui-keyring/options/types'; @@ -2565,7 +2565,7 @@ export interface KoniRequestSignatures { 'pri(transfer.getExistentialDeposit)': [RequestTransferExistentialDeposit, string]; 'pri(transfer.getMaxTransferable)': [RequestMaxTransferable, AmountData]; 'pri(transfer.subscribe)': [RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransfer]; - 'pri(transfer.confirmation.subscribe)': [RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransfer]; + 'pri(transfer.confirmation.subscribe)': [RequestSubscribeTransfer, ResponseSubscribeTransferConfirmation, ResponseSubscribeTransferConfirmation]; 'pri(subscription.cancel)': [string, boolean]; 'pri(freeBalance.get)': [RequestFreeBalance, AmountData]; 'pri(freeBalance.subscribe)': [RequestFreeBalance, AmountDataWithId, AmountDataWithId]; diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index ca595f992af..ab66dfa1adf 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -40,7 +40,7 @@ import { WALLET_CONNECT_EIP155_NAMESPACE } from '@subwallet/extension-base/servi import { isProposalExpired, isSupportWalletConnectChain, isSupportWalletConnectNamespace } from '@subwallet/extension-base/services/wallet-connect-service/helpers'; import { ResultApproveWalletConnectSession, WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types'; import { AccountsStore } from '@subwallet/extension-base/stores'; -import { BalanceJson, BitcoinFeeDetail, BitcoinFeeInfo, BitcoinFeeRate, BuyServiceInfo, BuyTokenInfo, DetermineUtxosForSpendArgs, EarningRewardJson, EvmEIP1995FeeOption, EvmFeeInfo, FeeChainType, FeeDetail, FeeInfo, GetFeeFunction, NominationPoolInfo, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, SubstrateFeeInfo, ValidateYieldProcessParams, YieldPoolType } from '@subwallet/extension-base/types'; +import { BalanceJson, BitcoinFeeDetail, BitcoinFeeInfo, BitcoinFeeRate, BuyServiceInfo, BuyTokenInfo, DetermineUtxosForSpendArgs, EarningRewardJson, EvmEIP1995FeeOption, EvmFeeInfo, FeeChainType, FeeCustom, FeeDetail, FeeInfo, FeeOption, GetFeeFunction, NominationPoolInfo, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation, SubstrateFeeInfo, ValidateYieldProcessParams, YieldPoolType } from '@subwallet/extension-base/types'; import { combineBitcoinFee, combineEthFee, convertSubjectInfoToAddresses, createTransactionFromRLP, determineUtxosForSpend, determineUtxosForSpendAll, filterUneconomicalUtxos, generateAccountProxyId, getSizeInfo, isAddressValidWithAuthType, isSameAddress, keyringGetAccounts, reformatAddress, signatureToHex, Transaction as QrTransaction, uniqueStringArray } from '@subwallet/extension-base/utils'; import { parseContractInput, parseEvmRlp } from '@subwallet/extension-base/utils/eth/parseTransaction'; import { balanceFormatter, BN_ZERO, formatNumber } from '@subwallet/extension-base/utils/number'; @@ -2806,91 +2806,70 @@ export default class KoniExtension { return convertData(freeBalance, fee); } - private async subscribeTransferableWhenConfirmation ({ address, chain, feeCustom, feeOption: _feeOptions, to, token, transferAll, value }: RequestSubscribeTransfer, id: string, port: chrome.runtime.Port): Promise { + private async subscribeTransferableWhenConfirmation ({ address, chain, feeCustom, feeOption: _feeOptions, to, token, value }: RequestSubscribeTransfer, id: string, port: chrome.runtime.Port): Promise { const cb = createSubscription<'pri(transfer.confirmation.subscribe)'>(id, port); - - const tokenInfo = token ? this.#koniState.chainService.getAssetBySlug(token) : this.#koniState.chainService.getNativeTokenInfo(chain); const freeBalanceSubject = new Subject(); const feeSubject = new Subject(); const feeType: FeeChainType = 'bitcoin'; - const convertData = async (freeBalance: AmountData, fee: BitcoinFeeInfo): Promise => { - let estimatedFee = '0'; + const convertData = async (freeBalance: AmountData, fee: BitcoinFeeInfo, feeOption?: FeeOption, feeCustom?: FeeCustom): Promise => { + const estimatedFee = '0'; let feeOptions: BitcoinFeeDetail | null = null; const amount = parseInt(value || '0'); - let maxTransferable = new BigN(freeBalance.value); + const neededUtxos = []; + let sum = new BigN(0); + let sizeInfo = null; try { const _fee = fee; const _feeCustom = feeCustom as BitcoinFeeRate; const combineFee = combineBitcoinFee(_fee, _feeOptions, _feeCustom); const bitcoinApi = this.#koniState.chainService.getBitcoinApi(chain); - const utxos = await getTransferableBitcoinUtxos(bitcoinApi, address); - const determineUtxosArgs: DetermineUtxosForSpendArgs = { - amount, - feeRate: combineFee.feeRate, - recipient: to || address, - sender: address, - utxos - }; - - const recipients = transferAll ? [address] : [address, to || address]; + let utxos = await getTransferableBitcoinUtxos(bitcoinApi, address); - const fallbackCalculate = (recipients: string[]) => { - const utxos = filterUneconomicalUtxos({ - utxos: determineUtxosArgs.utxos, - feeRate: determineUtxosArgs.feeRate, - recipients, - sender: determineUtxosArgs.sender - }); + const recipients = [address, to || address]; - const { txVBytes: vSize } = getSizeInfo({ - inputLength: utxos.length || 1, - sender: address, - recipients - }); - - return { - vSize, - maxTransferable: utxos.reduce((previous, input) => previous.plus(input.value), new BigN(0)), - estimatedFee: Math.ceil(determineUtxosArgs.feeRate * vSize).toString() - }; - }; - - try { - const { fee: _estimatedFee, inputs } = transferAll ? determineUtxosForSpendAll(determineUtxosArgs) : determineUtxosForSpend(determineUtxosArgs); - - maxTransferable = inputs.reduce((previous, input) => previous.plus(input.value), new BigN(0)); + utxos = utxos.sort((a, b) => b.value - a.value); + const filteredUtxos = filterUneconomicalUtxos({ + utxos, + feeRate: combineFee.feeRate, + recipients, + sender: address + }); - const { txVBytes: vSize } = getSizeInfo({ - inputLength: inputs.length, + for (const utxo of filteredUtxos) { + sizeInfo = getSizeInfo({ + inputLength: neededUtxos.length, sender: address, recipients }); - estimatedFee = new BigN(_estimatedFee).toFixed(0); - feeOptions = { - ..._fee, - estimatedFee, - vSize - }; - } catch (_e) { - const fb = fallbackCalculate([to || address]); + const currentValue = new BigN(amount).plus(Math.ceil(sizeInfo.txVBytes * combineFee.feeRate)); - maxTransferable = fb.maxTransferable; + if (sum.gte(currentValue)) { + break; + } - if (!feeOptions) { - const fb = fallbackCalculate([address, to || address]); + sum = sum.plus(utxo.value); + neededUtxos.push(utxo); + } - estimatedFee = fb.estimatedFee; + // re calculate + sizeInfo = getSizeInfo({ + inputLength: neededUtxos.length, + sender: address, + recipients + }); - feeOptions = { - ..._fee, - estimatedFee, - vSize: fb.vSize - }; - } + if (!sizeInfo) { + throw new BitcoinProviderError(BitcoinProviderErrorType.INTERNAL_ERROR); } + + feeOptions = { + ...fee, + vSize: sizeInfo.txVBytes, + estimatedFee: Math.ceil(combineFee.feeRate * sizeInfo.txVBytes).toFixed(0) + }; } catch (e) { feeOptions = { ...fee, @@ -2901,17 +2880,11 @@ export default class KoniExtension { console.warn('Unable to estimate fee', e); } - if (maxTransferable.lt(new BigN(estimatedFee).plus(new BigN(amount)))) { + if (new BigN(freeBalance.value).lt(new BigN(estimatedFee).plus(new BigN(amount)))) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Insufficient balance')); } - maxTransferable = maxTransferable - .minus(new BigN(estimatedFee)); - return { - maxTransferable: !_isNativeToken(tokenInfo) - ? freeBalance.value - : maxTransferable.gt(BN_ZERO) ? (maxTransferable.toFixed(0) || '0') : '0', feeOptions: feeOptions as FeeDetail, feeType, id @@ -2924,7 +2897,7 @@ export default class KoniExtension { }) .subscribe({ next: ({ fee, freeBalance }) => { - convertData(freeBalance, fee) + convertData(freeBalance, fee, _feeOptions, feeCustom) .then(cb) .catch(console.error); } @@ -2953,7 +2926,7 @@ export default class KoniExtension { this.cancelSubscription(id); }); - return convertData(freeBalance, fee as BitcoinFeeInfo); + return convertData(freeBalance, fee as BitcoinFeeInfo, _feeOptions, feeCustom); } private async subscribeAddressFreeBalance ({ address, networkKey, token }: RequestFreeBalance, id: string, port: chrome.runtime.Port): Promise { diff --git a/packages/extension-base/src/types/balance/transfer.ts b/packages/extension-base/src/types/balance/transfer.ts index b3b96fad746..be39381cf45 100644 --- a/packages/extension-base/src/types/balance/transfer.ts +++ b/packages/extension-base/src/types/balance/transfer.ts @@ -23,6 +23,8 @@ export interface ResponseSubscribeTransfer { feeType: FeeChainType; } +export type ResponseSubscribeTransferConfirmation = Omit; + export interface RequestSubmitTransfer extends BaseRequestSign, TransactionFee { chain: string; from: string; diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx index acfa82ef088..9da934cdcaa 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx @@ -3,8 +3,8 @@ import { _ChainAsset } from '@subwallet/chain-list/types'; import { BitcoinSendTransactionRequest, ConfirmationsQueueItem } from '@subwallet/extension-base/background/KoniTypes'; -import { BitcoinFeeDetail, RequestSubmitTransferWithId, ResponseSubscribeTransfer, TransactionFee } from '@subwallet/extension-base/types'; -import { BN_ZERO, getDomainFromUrl } from '@subwallet/extension-base/utils'; +import { BitcoinFeeDetail, RequestSubmitTransferWithId, ResponseSubscribeTransferConfirmation, TransactionFee } from '@subwallet/extension-base/types'; +import { getDomainFromUrl } from '@subwallet/extension-base/utils'; import { BitcoinFeeSelector, MetaInfo } from '@subwallet/extension-koni-ui/components'; import { RenderFieldNodeParams } from '@subwallet/extension-koni-ui/components/Field/TransactionFee/BitcoinFeeSelector'; import { useGetAccountByAddress, useNotification } from '@subwallet/extension-koni-ui/hooks'; @@ -53,8 +53,7 @@ function Component ({ className, request, type }: Props) { value: value?.toString() || '0' }); const [isFetchingInfo, setIsFetchingInfo] = useState(false); - const [isTransferAll, setIsTransferAll] = useState(false); - const [transferInfo, setTransferInfo] = useState(); + const [transferInfo, setTransferInfo] = useState(); const [transactionFeeInfo, setTransactionFeeInfo] = useState(undefined); const [isErrorTransaction, setIsErrorTransaction] = useState(false); const notify = useNotification(); @@ -114,15 +113,6 @@ function Component ({ className, request, type }: Props) { setTransactionInfo((prevState) => ({ ...prevState, ...transactionFeeInfo })); }, [transactionFeeInfo]); - useEffect(() => { - const bnTransferAmount = new BigN(transferAmountValue || '0'); - const bnMaxTransfer = new BigN(transferInfo?.maxTransferable || '0'); - - if (bnTransferAmount.gt(BN_ZERO) && bnTransferAmount.eq(bnMaxTransfer)) { - setIsTransferAll(true); - } - }, [transferInfo, transferAmountValue]); - useEffect(() => { let cancel = false; let id = ''; @@ -130,7 +120,7 @@ function Component ({ className, request, type }: Props) { setIsFetchingInfo(true); - const callback = (transferInfo: ResponseSubscribeTransfer) => { + const callback = (transferInfo: ResponseSubscribeTransferConfirmation) => { if (!cancel) { setTransferInfo(transferInfo); id = transferInfo.id; @@ -149,7 +139,7 @@ function Component ({ className, request, type }: Props) { feeOption: transactionFeeInfo?.feeOption, feeCustom: transactionFeeInfo?.feeCustom, value: transferAmountValue || '0', - transferAll: isTransferAll, + transferAll: false, to: toValue }, callback) .then(callback) @@ -174,7 +164,7 @@ function Component ({ className, request, type }: Props) { clearTimeout(timeout); id && cancelSubscription(id).catch(console.error); }; - }, [assetRegistry, assetValue, chainValue, fromValue, toValue, transactionFeeInfo, transferAmountValue, isTransferAll, notify, t]); + }, [assetRegistry, assetValue, chainValue, fromValue, toValue, transactionFeeInfo, transferAmountValue, notify, t]); return ( <> diff --git a/packages/extension-koni-ui/src/messaging/transaction/transfer.ts b/packages/extension-koni-ui/src/messaging/transaction/transfer.ts index 9dbd51fb9b9..24397fde140 100644 --- a/packages/extension-koni-ui/src/messaging/transaction/transfer.ts +++ b/packages/extension-koni-ui/src/messaging/transaction/transfer.ts @@ -3,7 +3,7 @@ import { AmountData, RequestCrossChainTransfer, RequestMaxTransferable, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, SupportTransferResponse } from '@subwallet/extension-base/background/KoniTypes'; import { BitcoinTransactionData, SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; -import { RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, ResponseSubscribeTransfer } from '@subwallet/extension-base/types'; +import { RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation } from '@subwallet/extension-base/types'; import { sendMessage } from '../base'; @@ -43,6 +43,6 @@ export async function subscribeMaxTransfer (request: RequestSubscribeTransfer, c return sendMessage('pri(transfer.subscribe)', request, callback); } -export async function subscribeTransferWhenConfirmation (request: RequestSubscribeTransfer, callback: (data: ResponseSubscribeTransfer) => void): Promise { +export async function subscribeTransferWhenConfirmation (request: RequestSubscribeTransfer, callback: (data: ResponseSubscribeTransferConfirmation) => void): Promise { return sendMessage('pri(transfer.confirmation.subscribe)', request, callback); } From 6ee448d351faf857cbbb1d3e9f170bdf06dc224d Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Thu, 4 Jul 2024 14:38:52 +0700 Subject: [PATCH 07/11] [Issue 228] [fix] Refactor the code for sendTransfer in the new workflow #3 --- .../src/koni/background/handlers/Extension.ts | 100 ++++++++++++------ .../src/types/balance/transfer.ts | 4 +- ...coinSendTransactionRequestConfirmation.tsx | 9 +- 3 files changed, 78 insertions(+), 35 deletions(-) diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index ab66dfa1adf..dc51c294bd0 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -4,11 +4,10 @@ import { Common } from '@ethereumjs/common'; import { LegacyTransaction } from '@ethereumjs/tx'; import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from '@subwallet/chain-list/types'; -import { BitcoinProviderError } from '@subwallet/extension-base/background/errors/BitcoinProviderError'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { withErrorLog } from '@subwallet/extension-base/background/handlers/helpers'; import { createSubscription } from '@subwallet/extension-base/background/handlers/subscriptions'; -import { AccountExternalError, AccountExternalErrorCode, AccountProxiesWithCurrentProxy, AccountsWithCurrentAddress, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BasicTxErrorType, BasicTxWarningCode, BitcoinProviderErrorType, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CreateDeriveAccountInfo, CronReloadRequest, CrowdloanJson, CurrentAccountInfo, DeriveAccountInfo, ExternalRequestPromiseStatus, ExtrinsicType, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, OptionInputAddress, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateSuriV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAccountMeta, RequestAccountProxyCreateSuri, RequestAccountProxyEdit, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerAccountProxy, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBatchRestoreV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConnectWalletConnect, RequestCrossChainTransfer, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDeriveCreateMultiple, RequestDeriveCreateV2, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetDeriveAccounts, RequestGetTransaction, RequestJsonRestoreV2, RequestKeyringExportAccountProxyMnemonic, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveRecentAccount, RequestSeedCreateV2, RequestSeedValidateV2, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateSuriV2, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseAccountMeta, ResponseChangeMasterPassword, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseFindRawMetadata, ResponseGetDeriveAccounts, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponsePrivateKeyValidateV2, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSeedCreateV2, ResponseSeedValidateV2, ResponseSubscribeHistory, ResponseUnlockKeyring, StakingJson, StakingRewardJson, StakingTxErrorType, StakingType, SupportTransferResponse, ThemeNames, TransactionHistoryItem, TransactionResponse, TransferTxErrorType, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountExternalError, AccountExternalErrorCode, AccountProxiesWithCurrentProxy, AccountsWithCurrentAddress, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BasicTxErrorType, BasicTxWarningCode, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CreateDeriveAccountInfo, CronReloadRequest, CrowdloanJson, CurrentAccountInfo, DeriveAccountInfo, ExternalRequestPromiseStatus, ExtrinsicType, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, OptionInputAddress, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateSuriV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAccountMeta, RequestAccountProxyCreateSuri, RequestAccountProxyEdit, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerAccountProxy, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBatchRestoreV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConnectWalletConnect, RequestCrossChainTransfer, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDeriveCreateMultiple, RequestDeriveCreateV2, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetDeriveAccounts, RequestGetTransaction, RequestJsonRestoreV2, RequestKeyringExportAccountProxyMnemonic, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveRecentAccount, RequestSeedCreateV2, RequestSeedValidateV2, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateSuriV2, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseAccountMeta, ResponseChangeMasterPassword, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseFindRawMetadata, ResponseGetDeriveAccounts, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponsePrivateKeyValidateV2, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSeedCreateV2, ResponseSeedValidateV2, ResponseSubscribeHistory, ResponseUnlockKeyring, StakingJson, StakingRewardJson, StakingTxErrorType, StakingType, SupportTransferResponse, ThemeNames, TransactionHistoryItem, TransactionResponse, TransferTxErrorType, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson, AccountProxy, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountProxy, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeCancel, RequestAuthorizeReject, RequestBatchRestore, RequestCurrentAccountAddress, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApproveSignature, RequestSigningCancel, RequestTypes, ResponseAccountExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseType, SigningRequest, WindowOpenParams } from '@subwallet/extension-base/background/types'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { ALL_ACCOUNT_KEY, ALL_GENESIS_HASH, BTC_DUST_AMOUNT, SUPPORT_KEYPAIR_TYPES, XCM_FEE_RATIO, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants'; @@ -2811,6 +2810,7 @@ export default class KoniExtension { const freeBalanceSubject = new Subject(); const feeSubject = new Subject(); const feeType: FeeChainType = 'bitcoin'; + let error: string | undefined; const convertData = async (freeBalance: AmountData, fee: BitcoinFeeInfo, feeOption?: FeeOption, feeCustom?: FeeCustom): Promise => { const estimatedFee = '0'; @@ -2827,49 +2827,81 @@ export default class KoniExtension { const bitcoinApi = this.#koniState.chainService.getBitcoinApi(chain); let utxos = await getTransferableBitcoinUtxos(bitcoinApi, address); + const fallbackCalculate = (recipients: string[]) => { + utxos = filterUneconomicalUtxos({ + utxos, + feeRate: combineFee.feeRate, + recipients, + sender: address + }); + + const { txVBytes: vSize } = getSizeInfo({ + inputLength: utxos.length || 1, + sender: address, + recipients + }); + + return { + vSize, + estimatedFee: Math.ceil(combineFee.feeRate * vSize).toString() + }; + }; + const recipients = [address, to || address]; - utxos = utxos.sort((a, b) => b.value - a.value); - const filteredUtxos = filterUneconomicalUtxos({ - utxos, - feeRate: combineFee.feeRate, - recipients, - sender: address - }); + try { + utxos = utxos.sort((a, b) => b.value - a.value); + const filteredUtxos = filterUneconomicalUtxos({ + utxos, + feeRate: combineFee.feeRate, + recipients, + sender: address + }); + + for (const utxo of filteredUtxos) { + sizeInfo = getSizeInfo({ + inputLength: neededUtxos.length, + sender: address, + recipients + }); + + const currentValue = new BigN(amount).plus(Math.ceil(sizeInfo.txVBytes * combineFee.feeRate)); + + if (sum.gte(currentValue)) { + break; + } - for (const utxo of filteredUtxos) { + sum = sum.plus(utxo.value); + neededUtxos.push(utxo); + } + + // re calculate sizeInfo = getSizeInfo({ inputLength: neededUtxos.length, sender: address, recipients }); - const currentValue = new BigN(amount).plus(Math.ceil(sizeInfo.txVBytes * combineFee.feeRate)); - - if (sum.gte(currentValue)) { - break; + if (!sizeInfo) { + throw new Error('Insufficient funds'); } - sum = sum.plus(utxo.value); - neededUtxos.push(utxo); - } - - // re calculate - sizeInfo = getSizeInfo({ - inputLength: neededUtxos.length, - sender: address, - recipients - }); + feeOptions = { + ...fee, + vSize: sizeInfo.txVBytes, + estimatedFee: Math.ceil(combineFee.feeRate * sizeInfo.txVBytes).toFixed(0) + }; + } catch (e) { + if (!feeOptions) { + const fb = fallbackCalculate(recipients); - if (!sizeInfo) { - throw new BitcoinProviderError(BitcoinProviderErrorType.INTERNAL_ERROR); + feeOptions = { + ..._fee, + estimatedFee: fb.estimatedFee, + vSize: fb.vSize + }; + } } - - feeOptions = { - ...fee, - vSize: sizeInfo.txVBytes, - estimatedFee: Math.ceil(combineFee.feeRate * sizeInfo.txVBytes).toFixed(0) - }; } catch (e) { feeOptions = { ...fee, @@ -2877,16 +2909,18 @@ export default class KoniExtension { vSize: 0 }; + error = (e as Error).message || e as string; console.warn('Unable to estimate fee', e); } if (new BigN(freeBalance.value).lt(new BigN(estimatedFee).plus(new BigN(amount)))) { - throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Insufficient balance')); + error = t('Insufficient balance'); } return { feeOptions: feeOptions as FeeDetail, feeType, + error, id }; }; diff --git a/packages/extension-base/src/types/balance/transfer.ts b/packages/extension-base/src/types/balance/transfer.ts index be39381cf45..3059821c934 100644 --- a/packages/extension-base/src/types/balance/transfer.ts +++ b/packages/extension-base/src/types/balance/transfer.ts @@ -23,7 +23,9 @@ export interface ResponseSubscribeTransfer { feeType: FeeChainType; } -export type ResponseSubscribeTransferConfirmation = Omit; +export interface ResponseSubscribeTransferConfirmation extends Omit { + error?: string; +} export interface RequestSubmitTransfer extends BaseRequestSign, TransactionFee { chain: string; diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx index 9da934cdcaa..6f3674645d3 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSendTransactionRequestConfirmation.tsx @@ -121,7 +121,14 @@ function Component ({ className, request, type }: Props) { setIsFetchingInfo(true); const callback = (transferInfo: ResponseSubscribeTransferConfirmation) => { - if (!cancel) { + if (transferInfo.error) { + notify({ + message: t(transferInfo.error), + type: 'error', + duration: 8 + }); + setIsErrorTransaction(true); + } else if (!cancel) { setTransferInfo(transferInfo); id = transferInfo.id; } else { From 616b50b4621ecaeebd0be78a1221e7a6b39f3074 Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Thu, 4 Jul 2024 15:11:08 +0700 Subject: [PATCH 08/11] [Issue 228] [fix] Refactor the code for sendTransfer in the new workflow #4 --- .../src/koni/background/handlers/Extension.ts | 102 +++++++----------- 1 file changed, 39 insertions(+), 63 deletions(-) diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index dc51c294bd0..8317ba29946 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -2813,7 +2813,7 @@ export default class KoniExtension { let error: string | undefined; const convertData = async (freeBalance: AmountData, fee: BitcoinFeeInfo, feeOption?: FeeOption, feeCustom?: FeeCustom): Promise => { - const estimatedFee = '0'; + let estimatedFee = '0'; let feeOptions: BitcoinFeeDetail | null = null; const amount = parseInt(value || '0'); const neededUtxos = []; @@ -2827,81 +2827,61 @@ export default class KoniExtension { const bitcoinApi = this.#koniState.chainService.getBitcoinApi(chain); let utxos = await getTransferableBitcoinUtxos(bitcoinApi, address); - const fallbackCalculate = (recipients: string[]) => { - utxos = filterUneconomicalUtxos({ - utxos, - feeRate: combineFee.feeRate, - recipients, - sender: address - }); + const recipients = [address, to || address]; - const { txVBytes: vSize } = getSizeInfo({ - inputLength: utxos.length || 1, + utxos = utxos.sort((a, b) => b.value - a.value); + const filteredUtxos = filterUneconomicalUtxos({ + utxos, + feeRate: combineFee.feeRate, + recipients, + sender: address + }); + + for (const utxo of filteredUtxos) { + sizeInfo = getSizeInfo({ + inputLength: neededUtxos.length, sender: address, recipients }); - return { - vSize, - estimatedFee: Math.ceil(combineFee.feeRate * vSize).toString() - }; - }; + const currentValue = new BigN(amount).plus(Math.ceil(sizeInfo.txVBytes * combineFee.feeRate)); - const recipients = [address, to || address]; - - try { - utxos = utxos.sort((a, b) => b.value - a.value); - const filteredUtxos = filterUneconomicalUtxos({ - utxos, - feeRate: combineFee.feeRate, - recipients, - sender: address - }); - - for (const utxo of filteredUtxos) { - sizeInfo = getSizeInfo({ - inputLength: neededUtxos.length, - sender: address, - recipients - }); - - const currentValue = new BigN(amount).plus(Math.ceil(sizeInfo.txVBytes * combineFee.feeRate)); + if (sum.gte(currentValue)) { + break; + } - if (sum.gte(currentValue)) { - break; - } + sum = sum.plus(utxo.value); + neededUtxos.push(utxo); + } - sum = sum.plus(utxo.value); - neededUtxos.push(utxo); - } + // re calculate + sizeInfo = getSizeInfo({ + inputLength: neededUtxos.length, + sender: address, + recipients + }); - // re calculate + if (!sizeInfo) { sizeInfo = getSizeInfo({ - inputLength: neededUtxos.length, + inputLength: utxos.length || 1, sender: address, recipients }); + } - if (!sizeInfo) { - throw new Error('Insufficient funds'); - } + estimatedFee = Math.ceil(sizeInfo.txVBytes * combineFee.feeRate).toString(); - feeOptions = { - ...fee, - vSize: sizeInfo.txVBytes, - estimatedFee: Math.ceil(combineFee.feeRate * sizeInfo.txVBytes).toFixed(0) - }; - } catch (e) { - if (!feeOptions) { - const fb = fallbackCalculate(recipients); + const amountLeft = sum.minus(amount).minus(new BigN(estimatedFee)); - feeOptions = { - ..._fee, - estimatedFee: fb.estimatedFee, - vSize: fb.vSize - }; - } + if (amountLeft.lte(0)) { + error = 'Insufficient balance'; } + + feeOptions = { + ...fee, + vSize: sizeInfo.txVBytes, + estimatedFee + }; } catch (e) { feeOptions = { ...fee, @@ -2913,10 +2893,6 @@ export default class KoniExtension { console.warn('Unable to estimate fee', e); } - if (new BigN(freeBalance.value).lt(new BigN(estimatedFee).plus(new BigN(amount)))) { - error = t('Insufficient balance'); - } - return { feeOptions: feeOptions as FeeDetail, feeType, From 89cda57483093303c8f571b17f5320f2ab6c85de Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Sat, 6 Jul 2024 06:12:11 +0700 Subject: [PATCH 09/11] [Issue 228] [fix] Recheck ui --- .../src/background/KoniTypes.ts | 7 +- .../src/koni/background/handlers/Extension.ts | 81 ++++++++++++++++++- .../src/koni/background/handlers/State.ts | 45 ++++++++--- .../handler/BitcoinRequestHandler.ts | 45 ++++++++--- .../src/services/transaction-service/index.ts | 7 +- .../src/types/balance/transfer.ts | 15 +++- .../parts/Detail/BaseDetailModal.tsx | 4 + .../Confirmations/parts/Sign/Bitcoin.tsx | 38 ++++++--- .../variants/BitcoinSignPsbtConfirmation.tsx | 20 +++-- .../src/messaging/transaction/transfer.ts | 6 +- 10 files changed, 218 insertions(+), 50 deletions(-) diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index daf7e42fcce..ace0915a031 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -13,7 +13,7 @@ import { _BitcoinApi, _ChainState, _EvmApi, _NetworkUpsertParams, _SubstrateApi, import { CrowdloanContributionsResponse } from '@subwallet/extension-base/services/subscan-service/types'; import { BitcoinTransactionData, SWTransactionResponse, SWTransactionResult } from '@subwallet/extension-base/services/transaction-service/types'; import { WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types'; -import { BalanceJson, BuyServiceInfo, BuyTokenInfo, EarningRewardHistoryItem, EarningRewardJson, EarningStatus, HandleYieldStepParams, LeavePoolAdditionalData, NominationPoolInfo, OptimalYieldPath, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseEarlyValidateYield, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation, SubmitYieldStepData, TokenApproveData, UnlockDotTransactionNft, UnstakingStatus, ValidateYieldProcessParams, YieldPoolInfo, YieldPositionInfo, YieldValidationStatus } from '@subwallet/extension-base/types'; +import { BalanceJson, BuyServiceInfo, BuyTokenInfo, EarningRewardHistoryItem, EarningRewardJson, EarningStatus, HandleYieldStepParams, LeavePoolAdditionalData, NominationPoolInfo, OptimalYieldPath, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitSignPsbtTransfer, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseEarlyValidateYield, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation, SubmitYieldStepData, TokenApproveData, UnlockDotTransactionNft, UnstakingStatus, ValidateYieldProcessParams, YieldPoolInfo, YieldPositionInfo, YieldValidationStatus } from '@subwallet/extension-base/types'; import { InjectedAccount, InjectedAccountWithMeta, MetadataDefBase } from '@subwallet/extension-inject/types'; import { KeypairType, KeyringPair$Json, KeyringPair$Meta } from '@subwallet/keyring/types'; import { KeyringOptions } from '@subwallet/ui-keyring/options/types'; @@ -1352,6 +1352,8 @@ export interface PsbtTransactionArg { export interface BitcoinSignPsbtPayload extends Omit{ txInput: PsbtTransactionArg[]; txOutput: PsbtTransactionArg[]; + to: string; + value: string; psbt: Psbt; tokenSlug: string; } @@ -1369,7 +1371,7 @@ export interface BitcoinSignPsbtRawRequest { allowedSighash?: SignatureHash[]; signAtIndex?: number | number[]; broadcast?: boolean; - network: 'mainnet' | 'testnet'; + network: string; account: string; } @@ -2574,6 +2576,7 @@ export interface KoniRequestSignatures { 'pri(accounts.checkTransfer)': [RequestCheckTransfer, ValidateTransactionResponse]; 'pri(accounts.transfer)': [RequestSubmitTransfer, SWTransactionResponse]; 'pri(accounts.transfer.after.confirmation)': [RequestSubmitTransferWithId, SWTransactionResponse]; + 'pri(accounts.psbt.transfer.after.confirmation)': [RequestSubmitSignPsbtTransfer, SWTransactionResponse]; 'pri(accounts.getBitcoinTransactionData)': [RequestSubmitTransfer, BitcoinTransactionData]; 'pri(accounts.checkCrossChainTransfer)': [RequestCheckCrossChainTransfer, ValidateTransactionResponse]; diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 8317ba29946..8b9c40cbb58 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -7,7 +7,7 @@ import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { withErrorLog } from '@subwallet/extension-base/background/handlers/helpers'; import { createSubscription } from '@subwallet/extension-base/background/handlers/subscriptions'; -import { AccountExternalError, AccountExternalErrorCode, AccountProxiesWithCurrentProxy, AccountsWithCurrentAddress, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BasicTxErrorType, BasicTxWarningCode, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CreateDeriveAccountInfo, CronReloadRequest, CrowdloanJson, CurrentAccountInfo, DeriveAccountInfo, ExternalRequestPromiseStatus, ExtrinsicType, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, OptionInputAddress, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateSuriV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAccountMeta, RequestAccountProxyCreateSuri, RequestAccountProxyEdit, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerAccountProxy, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBatchRestoreV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConnectWalletConnect, RequestCrossChainTransfer, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDeriveCreateMultiple, RequestDeriveCreateV2, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetDeriveAccounts, RequestGetTransaction, RequestJsonRestoreV2, RequestKeyringExportAccountProxyMnemonic, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveRecentAccount, RequestSeedCreateV2, RequestSeedValidateV2, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateSuriV2, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseAccountMeta, ResponseChangeMasterPassword, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseFindRawMetadata, ResponseGetDeriveAccounts, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponsePrivateKeyValidateV2, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSeedCreateV2, ResponseSeedValidateV2, ResponseSubscribeHistory, ResponseUnlockKeyring, StakingJson, StakingRewardJson, StakingTxErrorType, StakingType, SupportTransferResponse, ThemeNames, TransactionHistoryItem, TransactionResponse, TransferTxErrorType, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountExternalError, AccountExternalErrorCode, AccountProxiesWithCurrentProxy, AccountsWithCurrentAddress, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BasicTxErrorType, BasicTxWarningCode, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CreateDeriveAccountInfo, CronReloadRequest, CrowdloanJson, CurrentAccountInfo, DeriveAccountInfo, ExternalRequestPromiseStatus, ExtrinsicType, FeeData, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, OptionInputAddress, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateSuriV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAccountMeta, RequestAccountProxyCreateSuri, RequestAccountProxyEdit, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerAccountProxy, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBatchRestoreV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestConfirmationCompleteBitcoin, RequestConnectWalletConnect, RequestCrossChainTransfer, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDeriveCreateMultiple, RequestDeriveCreateV2, RequestDeriveCreateV3, RequestDeriveValidateV2, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetDeriveAccounts, RequestGetTransaction, RequestJsonRestoreV2, RequestKeyringExportAccountProxyMnemonic, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveRecentAccount, RequestSeedCreateV2, RequestSeedValidateV2, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateSuriV2, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseAccountMeta, ResponseChangeMasterPassword, ResponseCheckPublicAndSecretKey, ResponseDeriveValidateV2, ResponseFindRawMetadata, ResponseGetDeriveAccounts, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponsePrivateKeyValidateV2, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSeedCreateV2, ResponseSeedValidateV2, ResponseSubscribeHistory, ResponseUnlockKeyring, StakingJson, StakingRewardJson, StakingTxErrorType, StakingType, SupportTransferResponse, ThemeNames, TransactionHistoryItem, TransactionResponse, TransferTxErrorType, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson, AccountProxy, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountProxy, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeCancel, RequestAuthorizeReject, RequestBatchRestore, RequestCurrentAccountAddress, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApproveSignature, RequestSigningCancel, RequestTypes, ResponseAccountExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseType, SigningRequest, WindowOpenParams } from '@subwallet/extension-base/background/types'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { ALL_ACCOUNT_KEY, ALL_GENESIS_HASH, BTC_DUST_AMOUNT, SUPPORT_KEYPAIR_TYPES, XCM_FEE_RATIO, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants'; @@ -39,7 +39,7 @@ import { WALLET_CONNECT_EIP155_NAMESPACE } from '@subwallet/extension-base/servi import { isProposalExpired, isSupportWalletConnectChain, isSupportWalletConnectNamespace } from '@subwallet/extension-base/services/wallet-connect-service/helpers'; import { ResultApproveWalletConnectSession, WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types'; import { AccountsStore } from '@subwallet/extension-base/stores'; -import { BalanceJson, BitcoinFeeDetail, BitcoinFeeInfo, BitcoinFeeRate, BuyServiceInfo, BuyTokenInfo, DetermineUtxosForSpendArgs, EarningRewardJson, EvmEIP1995FeeOption, EvmFeeInfo, FeeChainType, FeeCustom, FeeDetail, FeeInfo, FeeOption, GetFeeFunction, NominationPoolInfo, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation, SubstrateFeeInfo, ValidateYieldProcessParams, YieldPoolType } from '@subwallet/extension-base/types'; +import { BalanceJson, BitcoinFeeDetail, BitcoinFeeInfo, BitcoinFeeRate, BuyServiceInfo, BuyTokenInfo, DetermineUtxosForSpendArgs, EarningRewardJson, EvmEIP1995FeeOption, EvmFeeInfo, FeeChainType, FeeCustom, FeeDetail, FeeInfo, FeeOption, GetFeeFunction, NominationPoolInfo, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestSubmitSignPsbtTransfer, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseGetYieldPoolTargets, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation, SubstrateFeeInfo, ValidateYieldProcessParams, YieldPoolType } from '@subwallet/extension-base/types'; import { combineBitcoinFee, combineEthFee, convertSubjectInfoToAddresses, createTransactionFromRLP, determineUtxosForSpend, determineUtxosForSpendAll, filterUneconomicalUtxos, generateAccountProxyId, getSizeInfo, isAddressValidWithAuthType, isSameAddress, keyringGetAccounts, reformatAddress, signatureToHex, Transaction as QrTransaction, uniqueStringArray } from '@subwallet/extension-base/utils'; import { parseContractInput, parseEvmRlp } from '@subwallet/extension-base/utils/eth/parseTransaction'; import { balanceFormatter, BN_ZERO, formatNumber } from '@subwallet/extension-base/utils/number'; @@ -2048,7 +2048,7 @@ export default class KoniExtension { const nativeTokenInfo = this.#koniState.getNativeTokenInfo(chain); const nativeTokenSlug: string = nativeTokenInfo.slug; const isTransferNativeToken = nativeTokenSlug === tokenSlug; - let chainType = ChainType.SUBSTRATE; + let chainType = ChainType.BITCOIN; const tokenBaseAmount: AmountData = { value: '0', symbol: tokenInfo.symbol, decimals: tokenInfo.decimals || 0 }; const transferAmount: AmountData = { ...tokenBaseAmount }; @@ -2118,6 +2118,79 @@ export default class KoniExtension { }); } + private async makePsbtTransferAfterConfirmation (inputData_: RequestSubmitSignPsbtTransfer): Promise { + const { chain, from, id, psbt, tokenSlug, txInput, txOutput, value } = inputData_; + let inputAmount = new BigN(0); + + const totalUtxoInput = txInput.reduce((total, { address, amount }) => { + if (!address || !amount) { + return total; + } + + if (isSameAddress(address, from)) { + inputAmount = new BigN(amount); + } + + return total.plus(new BigN(amount || 0)); + }, new BigN(0)); + + const totalUtxoOutput = txOutput.reduce((total, { address, amount }) => { + if (!address || !amount) { + return total; + } + + return total.plus(new BigN(amount)); + }, new BigN(0)); + + const estimateFeeValue = totalUtxoInput.minus(totalUtxoOutput).toString(); + const [errors, , , tokenInfo] = this.validateTransfer(tokenSlug, from, txOutput[0]?.address || '', value, false); + + const warnings: TransactionWarning[] = []; + + const chainInfo = this.#koniState.getChainInfo(chain); + const { decimals, symbol } = _getChainNativeTokenBasicInfo(chainInfo); + const estimateFee: FeeData = { + symbol, + decimals, + value: estimateFeeValue, + tooHigh: false + }; + + const nativeTokenInfo = this.#koniState.getNativeTokenInfo(chain); + const nativeTokenSlug: string = nativeTokenInfo.slug; + const isTransferNativeToken = nativeTokenSlug === tokenSlug; + const chainType = ChainType.BITCOIN; + + const tokenBaseAmount: AmountData = { value: inputData_.value, symbol: tokenInfo.symbol, decimals: tokenInfo.decimals || 0 }; + const transferAmount: AmountData = { ...tokenBaseAmount }; + + // Get native token amount + const freeBalance = await this.getAddressFreeBalance({ address: from, networkKey: chain, token: tokenSlug }); + + if (new BigN(freeBalance.value).lte(inputAmount)) { + throw new Error(t('Insufficient balance')); + } + + const transferNativeAmount = isTransferNativeToken ? transferAmount.value : '0'; + + return this.#koniState.transactionService.handleTransactionAfterConfirmation({ + id, + errors, + warnings, + address: from, + chain: chain, + estimateFee, + chainType, + transferNativeAmount, + transaction: psbt, + data: inputData_, + extrinsicType: isTransferNativeToken ? ExtrinsicType.TRANSFER_BALANCE : ExtrinsicType.TRANSFER_TOKEN, + ignoreWarnings: false, + isTransferAll: false, + edAsWarning: isTransferNativeToken + }); + } + private async getBitcoinTransactionData (inputData: RequestSubmitTransfer): Promise { const { chain, feeCustom, feeOption, from, to, transferAll, value } = inputData; @@ -5651,6 +5724,8 @@ export default class KoniExtension { return await this.makeTransfer(request as RequestSubmitTransfer); case 'pri(accounts.transfer.after.confirmation)': return await this.makeTransferAfterConfirmation(request as RequestSubmitTransfer); + case 'pri(accounts.psbt.transfer.after.confirmation)': + return await this.makePsbtTransferAfterConfirmation(request as RequestSubmitSignPsbtTransfer); case 'pri(accounts.crossChainTransfer)': return await this.makeCrossChainTransfer(request as RequestCrossChainTransfer); case 'pri(accounts.getBitcoinTransactionData)': diff --git a/packages/extension-base/src/koni/background/handlers/State.ts b/packages/extension-base/src/koni/background/handlers/State.ts index 2dc07b2ab61..0cf9f4fa7ae 100644 --- a/packages/extension-base/src/koni/background/handlers/State.ts +++ b/packages/extension-base/src/koni/background/handlers/State.ts @@ -40,7 +40,7 @@ import WalletConnectService from '@subwallet/extension-base/services/wallet-conn import { SWStorage } from '@subwallet/extension-base/storage'; import AccountRefStore from '@subwallet/extension-base/stores/AccountRef'; import { BalanceItem, BalanceMap, EvmFeeInfo } from '@subwallet/extension-base/types'; -import { isAccountAll, keyringGetAccounts, stripUrl, targetIsWeb } from '@subwallet/extension-base/utils'; +import { isAccountAll, isSameAddress, keyringGetAccounts, stripUrl, targetIsWeb } from '@subwallet/extension-base/utils'; import { isContractAddress, parseContractInput } from '@subwallet/extension-base/utils/eth/parseTransaction'; import { createPromiseHandler } from '@subwallet/extension-base/utils/promise'; import { MetadataDef, ProviderMeta } from '@subwallet/extension-inject/types'; @@ -50,6 +50,7 @@ import { KeypairType } from '@subwallet/keyring/types'; import { keyring } from '@subwallet/ui-keyring'; import BigN from 'bignumber.js'; import * as bitcoin from 'bitcoinjs-lib'; +import { Psbt } from 'bitcoinjs-lib'; import BN from 'bn.js'; import SimpleKeyring from 'eth-simple-keyring'; import { t } from 'i18next'; @@ -1236,7 +1237,7 @@ export default class KoniState { } public async bitcoinSignPspt (id: string, url: string, networkKey: string, method: string, params: BitcoinSignPsbtRawRequest, allowedAccounts: string[]): Promise { - const { account: address, allowedSighash, broadcast, network, psbt, signAtIndex } = params; + const { account: address, allowedSighash, broadcast, psbt, signAtIndex } = params; if (!psbt || !address) { throw new BitcoinProviderError(BitcoinProviderErrorType.INVALID_PARAMS, t('Not found payload to sign')); @@ -1275,28 +1276,46 @@ export default class KoniState { const network_ = networkKey === 'bitcoinTestnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; - const psbtGenerate = bitcoin.Psbt.fromHex(psbt, { + const psbtGenerate = Psbt.fromHex(psbt, { network: network_ }); - const tokenInfo = this.getNativeTokenInfo(networkKey); + const isExistedInput = (inputs: PsbtTransactionArg[], address: string) => inputs.findIndex(({ address: address_ }) => isSameAddress(address, address_ || '')); - const psbtInputData = psbtGenerate.data.inputs.map(({ witnessUtxo }) => { + const tokenInfo = this.getNativeTokenInfo(networkKey); + let to = ''; + let value = new BigN(0); + const psbtInputData = psbtGenerate.data.inputs.reduce((inputs, { witnessUtxo }) => { if (!witnessUtxo) { - return {}; + return inputs; } const address = bitcoin.address.fromOutputScript(witnessUtxo?.script, network_); + const existedInput = isExistedInput(inputs, address); - return { - address, - amount: witnessUtxo.value.toString() - } as PsbtTransactionArg; - }); + if (existedInput === -1) { + inputs.push({ + address, + amount: witnessUtxo.value.toString() + }); + } else { + inputs[existedInput] = { + ...inputs[existedInput], + amount: new BigN(inputs[existedInput].amount || 0).plus(new BigN(witnessUtxo.value.toString())).toString() + }; + } + + return inputs; + }, [] as PsbtTransactionArg[]); const psbtOutputData = psbtGenerate.txOutputs.map((output) => { const address = output.address || bitcoin.address.fromOutputScript(output.script, network_); + if (isExistedInput(psbtInputData, address) === -1) { + to = address; + value = value.plus(new BigN(output.value)); + } + return { address, amount: output.value.toString() @@ -1306,7 +1325,9 @@ export default class KoniState { const payload: BitcoinSignPsbtPayload = { psbt: psbtGenerate, broadcast: !!broadcast, - network, + value: value.toString(), + to, + network: networkKey, signAtIndex: isArray(signAtIndex) && signAtIndex.length === 0 ? undefined : signAtIndex, account: account.address, allowedSighash, diff --git a/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts b/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts index 7d1ec9eb2e7..75b92b03481 100644 --- a/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts +++ b/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts @@ -283,7 +283,7 @@ export default class BitcoinRequestHandler { private async signPsbt (request: ConfirmationDefinitionsBitcoin['bitcoinSignPsbtRequest'][0]): Promise { // Extract necessary information from the BitcoinSendTransactionRequest const { account, payload } = request.payload; - const { allowedSighash, broadcast, network, psbt, signAtIndex } = payload; + const { allowedSighash, broadcast, psbt, signAtIndex } = payload; // todo: validate type of the account @@ -299,8 +299,6 @@ export default class BitcoinRequestHandler { } const signAtIndexGenerate = signAtIndex ? (isArray(signAtIndex) ? signAtIndex : [signAtIndex]) : [...(Array(psbt.inputCount) as number[])].map((_, i) => i); - - console.log(signAtIndexGenerate); // Sign the Psbt using the pair's bitcoin object const psptSignedTransaction = pair.bitcoin.signTransaction(psbt, signAtIndexGenerate, allowedSighash); @@ -314,18 +312,45 @@ export default class BitcoinRequestHandler { }; } + const transaction = this.#transactionService.getTransaction(request.id); + + console.log(transaction); + const { chain, emitterTransaction, id } = transaction; + const chainInfo = this.#chainService.getChainInfoByKey(chain); + const eventData: TransactionEventResponse = { + id, + errors: [], + warnings: [], + extrinsicHash: id + }; + + if (!emitterTransaction) { + throw new BitcoinProviderError(BitcoinProviderErrorType.INTERNAL_ERROR); + } + psptSignedTransaction.finalizeAllInputs(); - const chain = network === 'mainnet' ? 'bitcoin' : 'bitcoinTestnet'; + const hexTransaction = psptSignedTransaction.extractTransaction().toHex(); - const txid = await this.#chainService.getBitcoinApi(chain).api.simpleSendRawTransaction(psptSignedTransaction.extractTransaction().toHex()); + this.#transactionService.emitterEventTransaction(emitterTransaction, eventData, chainInfo.slug, hexTransaction); + const { promise, reject, resolve } = createPromiseHandler(); - console.log('TXID', txid); + emitterTransaction.on('extrinsicHash', (data) => { + if (!data.extrinsicHash) { + reject(BitcoinProviderErrorType.INTERNAL_ERROR); + } else { + resolve({ + psbt: psptSignedTransaction.toHex(), + txid: data.extrinsicHash + }); + } + }); - return { - psbt: psptSignedTransaction.toHex(), - txid - }; + emitterTransaction.on('error', (error) => { + reject(error); + }); + + return promise; } private async decorateResultBitcoin (t: T, request: ConfirmationDefinitionsBitcoin[T][0], result: ConfirmationDefinitionsBitcoin[T][1]) { diff --git a/packages/extension-base/src/services/transaction-service/index.ts b/packages/extension-base/src/services/transaction-service/index.ts index 2638fddd59e..e26353d7bd5 100644 --- a/packages/extension-base/src/services/transaction-service/index.ts +++ b/packages/extension-base/src/services/transaction-service/index.ts @@ -104,6 +104,7 @@ export default class TransactionService { address, chain, edAsWarning, + estimateFee: estimateFee_, extrinsicType, feeCustom, feeOption, @@ -130,7 +131,7 @@ export default class TransactionService { const chainInfo = this.state.chainService.getChainInfoByKey(chain); // Estimate fee - const estimateFee: FeeData = { + const estimateFee: FeeData = estimateFee_ || { symbol: '', decimals: 0, value: '', @@ -147,7 +148,7 @@ export default class TransactionService { const id = getId(); - if (transaction) { + if (transaction && !estimateFee_) { try { if (isSubstrateTransaction(transaction)) { estimateFee.value = (await transaction.paymentInfo(address)).partialFee.toString(); @@ -388,7 +389,7 @@ export default class TransactionService { const emitter = new EventEmitter(); // Fill transaction default info - const transactionUpdated = this.fillTransactionDefaultInfo(transaction); + const transactionUpdated = this.fillTransactionDefaultInfo(validatedTransaction); // Add Transaction transactionsSubject[transactionUpdated.id] = { ...transactionUpdated, emitterTransaction: emitter }; diff --git a/packages/extension-base/src/types/balance/transfer.ts b/packages/extension-base/src/types/balance/transfer.ts index 3059821c934..ae38a49811e 100644 --- a/packages/extension-base/src/types/balance/transfer.ts +++ b/packages/extension-base/src/types/balance/transfer.ts @@ -1,7 +1,8 @@ // Copyright 2019-2022 @subwallet/extension-base authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { BaseRequestSign } from '@subwallet/extension-base/background/KoniTypes'; +import { BaseRequestSign, PsbtTransactionArg } from '@subwallet/extension-base/background/KoniTypes'; +import { Psbt } from 'bitcoinjs-lib'; import { FeeChainType, FeeDetail, TransactionFee } from '../fee'; @@ -39,3 +40,15 @@ export interface RequestSubmitTransfer extends BaseRequestSign, TransactionFee { export interface RequestSubmitTransferWithId extends RequestSubmitTransfer{ id?: string; } + +export interface RequestSubmitSignPsbtTransfer extends BaseRequestSign { + id: string; + chain: string; + from: string; + to: string; + value: string; + txInput: PsbtTransactionArg[]; + txOutput: PsbtTransactionArg[]; + tokenSlug: string; + psbt: Psbt; +} diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/BaseDetailModal.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/BaseDetailModal.tsx index 0f847bdd651..47726c097b1 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/BaseDetailModal.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Detail/BaseDetailModal.tsx @@ -64,6 +64,10 @@ const BaseDetailModal = styled(Component)(({ theme: { token } }: Props) = '.__label': { textTransform: 'capitalize' + }, + + '.ant-web3-block-right-item': { + marginRight: 0 } }; }); diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx index b5d24e758e5..2265d7ce6d2 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx @@ -1,12 +1,12 @@ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { BitcoinSignatureRequest, ConfirmationDefinitionsBitcoin, ConfirmationResult, EvmSendTransactionRequest, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; +import { BitcoinSignatureRequest, BitcoinSignPsbtRequest, ConfirmationDefinitionsBitcoin, ConfirmationResult, EvmSendTransactionRequest, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; import { RequestSubmitTransferWithId } from '@subwallet/extension-base/types'; import { wait } from '@subwallet/extension-base/utils'; import { CONFIRMATION_QR_MODAL } from '@subwallet/extension-koni-ui/constants'; import { useGetChainInfoByChainId, useLedger, useNotification, useUnlockChecker } from '@subwallet/extension-koni-ui/hooks'; -import { completeConfirmationBitcoin, makeTransferAfterConfirmation } from '@subwallet/extension-koni-ui/messaging'; +import { completeConfirmationBitcoin, makePSBTTransferAfterConfirmation, makeTransferAfterConfirmation } from '@subwallet/extension-koni-ui/messaging'; import { AccountSignMode, BitcoinSignatureSupportType, PhosphorIcon, SigData, ThemeProps } from '@subwallet/extension-koni-ui/types'; import { getSignMode, isBitcoinMessage, removeTransactionPersist } from '@subwallet/extension-koni-ui/utils'; import { Button, Icon, ModalContext } from '@subwallet/react-ui'; @@ -55,7 +55,7 @@ const handleSignature = async (type: BitcoinSignatureSupportType, id: string, si const Component: React.FC = (props: Props) => { const { canSign, className, editedPayload, extrinsicType, id, payload, type } = props; const { payload: { hashPayload } } = payload; - const account = (payload.payload as BitcoinSignatureRequest).account; + const { account } = (payload.payload as BitcoinSignatureRequest); const chainId = (payload.payload as EvmSendTransactionRequest)?.chainId || 1; const { t } = useTranslation(); @@ -110,15 +110,31 @@ const Component: React.FC = (props: Props) => { const onApprovePassword = useCallback(() => { setLoading(true); - (type === 'bitcoinSendTransactionRequestAfterConfirmation' && editedPayload ? makeTransferAfterConfirmation(editedPayload) : wait(1000)) - .then(() => { - console.log('complete', type, id); - handleConfirm(type, id, '').finally(() => { - setLoading(false); - }); - }) + + const promise = async () => { + if (type === 'bitcoinSendTransactionRequestAfterConfirmation' && editedPayload) { + await makeTransferAfterConfirmation(editedPayload); + } else if (type === 'bitcoinSignPsbtRequest') { + const { payload: { account, broadcast, network, psbt, to, tokenSlug, txInput, txOutput, value } } = payload.payload as BitcoinSignPsbtRequest; + + if (broadcast) { + await makePSBTTransferAfterConfirmation({ id, chain: network, txOutput, txInput, tokenSlug, psbt, from: account, to, value }); + } else { + await wait(1000); + } + } else { + await wait(1000); + } + }; + + promise().then(() => { + console.log('complete', type, id); + handleConfirm(type, id, '').finally(() => { + setLoading(false); + }); + }) .catch(console.error); - }, [editedPayload, id, type]); + }, [editedPayload, id, payload.payload, type]); const onApproveSignature = useCallback((signature: SigData) => { setLoading(true); diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx index ea4f8f6b360..664ec0d7e59 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx @@ -8,6 +8,7 @@ import { useOpenDetailModal } from '@subwallet/extension-koni-ui/hooks'; import { BitcoinSignArea } from '@subwallet/extension-koni-ui/Popup/Confirmations/parts'; import { RootState } from '@subwallet/extension-koni-ui/stores'; import { BitcoinSignatureSupportType, ThemeProps } from '@subwallet/extension-koni-ui/types'; +import { findAccountByAddress } from '@subwallet/extension-koni-ui/utils'; import { Button, Number } from '@subwallet/react-ui'; import CN from 'classnames'; import React, { useCallback, useMemo } from 'react'; @@ -27,18 +28,22 @@ function Component ({ className, request, type }: Props) { const { t } = useTranslation(); const { account } = payload; const { tokenSlug, txInput, txOutput } = request.payload.payload; + const accounts = useSelector((state: RootState) => state.accountState.accounts); const assetRegistry = useSelector((root: RootState) => root.assetRegistry.assetRegistry); const onClickDetail = useOpenDetailModal(); const assetInfo: _ChainAsset | undefined = useMemo(() => { return assetRegistry[tokenSlug]; }, [assetRegistry, tokenSlug]); - const renderAccount = useCallback((accounts: PsbtTransactionArg[]) => { + const renderAccount = useCallback((accountsPsbt: PsbtTransactionArg[]) => { return ( - <> +
{ - accounts.map(({ address, amount }) => - { + const account = findAccountByAddress(accounts, address); + + return ( : <>} - /> + />); + } ) } - +
); - }, [assetInfo.decimals, assetInfo.symbol]); + }, [accounts, assetInfo.decimals, assetInfo.symbol]); return ( <> diff --git a/packages/extension-koni-ui/src/messaging/transaction/transfer.ts b/packages/extension-koni-ui/src/messaging/transaction/transfer.ts index 24397fde140..8f2176ae128 100644 --- a/packages/extension-koni-ui/src/messaging/transaction/transfer.ts +++ b/packages/extension-koni-ui/src/messaging/transaction/transfer.ts @@ -3,7 +3,7 @@ import { AmountData, RequestCrossChainTransfer, RequestMaxTransferable, RequestTransferCheckReferenceCount, RequestTransferCheckSupporting, RequestTransferExistentialDeposit, SupportTransferResponse } from '@subwallet/extension-base/background/KoniTypes'; import { BitcoinTransactionData, SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; -import { RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation } from '@subwallet/extension-base/types'; +import { RequestSubmitSignPsbtTransfer, RequestSubmitTransfer, RequestSubmitTransferWithId, RequestSubscribeTransfer, ResponseSubscribeTransfer, ResponseSubscribeTransferConfirmation } from '@subwallet/extension-base/types'; import { sendMessage } from '../base'; @@ -15,6 +15,10 @@ export async function makeTransferAfterConfirmation (request: RequestSubmitTrans return sendMessage('pri(accounts.transfer.after.confirmation)', request); } +export async function makePSBTTransferAfterConfirmation (request: RequestSubmitSignPsbtTransfer): Promise { + return sendMessage('pri(accounts.psbt.transfer.after.confirmation)', request); +} + export async function makeCrossChainTransfer (request: RequestCrossChainTransfer): Promise { return sendMessage('pri(accounts.crossChainTransfer)', request); } From fd86aee5ee626dfb51d9bb97531ec180cfa42ff1 Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Sat, 6 Jul 2024 15:36:31 +0700 Subject: [PATCH 10/11] [Issue 228] [fix] Recheck signPsbt flow --- .../src/background/KoniTypes.ts | 4 +- .../src/koni/background/handlers/Extension.ts | 12 +++-- .../src/koni/background/handlers/State.ts | 42 ++++++++++------- .../handler/BitcoinRequestHandler.ts | 47 +++++++++++++++---- .../src/services/transaction-service/index.ts | 7 +++ .../Confirmations/parts/Sign/Bitcoin.tsx | 19 ++++++-- .../variants/BitcoinSignPsbtConfirmation.tsx | 1 - .../src/messaging/transaction/transfer.ts | 6 +-- 8 files changed, 97 insertions(+), 41 deletions(-) diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index ace0915a031..cb7e4fd2c7f 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -2575,8 +2575,8 @@ export interface KoniRequestSignatures { // Transfer 'pri(accounts.checkTransfer)': [RequestCheckTransfer, ValidateTransactionResponse]; 'pri(accounts.transfer)': [RequestSubmitTransfer, SWTransactionResponse]; - 'pri(accounts.transfer.after.confirmation)': [RequestSubmitTransferWithId, SWTransactionResponse]; - 'pri(accounts.psbt.transfer.after.confirmation)': [RequestSubmitSignPsbtTransfer, SWTransactionResponse]; + 'pri(accounts.bitcoin.dapp.transfer.confirmation)': [RequestSubmitTransferWithId, SWTransactionResponse]; + 'pri(accounts.psbt.transfer.confirmation)': [RequestSubmitSignPsbtTransfer, SWTransactionResponse]; 'pri(accounts.getBitcoinTransactionData)': [RequestSubmitTransfer, BitcoinTransactionData]; 'pri(accounts.checkCrossChainTransfer)': [RequestCheckCrossChainTransfer, ValidateTransactionResponse]; diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 8b9c40cbb58..7c2471f08cf 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -2038,7 +2038,7 @@ export default class KoniExtension { }); } - private async makeTransferAfterConfirmation (inputData: RequestSubmitTransferWithId): Promise { + private async makeBitcoinDappTransferConfirmation (inputData: RequestSubmitTransferWithId): Promise { const { chain, feeCustom, feeOption, from, id, to, tokenSlug, transferAll, value } = inputData; const [errors, , , tokenInfo] = this.validateTransfer(tokenSlug, from, to, value, transferAll); @@ -2167,7 +2167,9 @@ export default class KoniExtension { // Get native token amount const freeBalance = await this.getAddressFreeBalance({ address: from, networkKey: chain, token: tokenSlug }); - if (new BigN(freeBalance.value).lte(inputAmount)) { + console.log(freeBalance, inputAmount.toString(), '123123'); + + if (new BigN(freeBalance.value).lt(inputAmount)) { throw new Error(t('Insufficient balance')); } @@ -5722,9 +5724,9 @@ export default class KoniExtension { /// Transfer case 'pri(accounts.transfer)': return await this.makeTransfer(request as RequestSubmitTransfer); - case 'pri(accounts.transfer.after.confirmation)': - return await this.makeTransferAfterConfirmation(request as RequestSubmitTransfer); - case 'pri(accounts.psbt.transfer.after.confirmation)': + case 'pri(accounts.bitcoin.dapp.transfer.confirmation)': + return await this.makeBitcoinDappTransferConfirmation(request as RequestSubmitTransfer); + case 'pri(accounts.psbt.transfer.confirmation)': return await this.makePsbtTransferAfterConfirmation(request as RequestSubmitSignPsbtTransfer); case 'pri(accounts.crossChainTransfer)': return await this.makeCrossChainTransfer(request as RequestCrossChainTransfer); diff --git a/packages/extension-base/src/koni/background/handlers/State.ts b/packages/extension-base/src/koni/background/handlers/State.ts index 0cf9f4fa7ae..f0345cbe00f 100644 --- a/packages/extension-base/src/koni/background/handlers/State.ts +++ b/packages/extension-base/src/koni/background/handlers/State.ts @@ -1285,31 +1285,41 @@ export default class KoniState { const tokenInfo = this.getNativeTokenInfo(networkKey); let to = ''; let value = new BigN(0); - const psbtInputData = psbtGenerate.data.inputs.reduce((inputs, { witnessUtxo }) => { - if (!witnessUtxo) { - return inputs; - } - - const address = bitcoin.address.fromOutputScript(witnessUtxo?.script, network_); - const existedInput = isExistedInput(inputs, address); + const psbtInputData = psbtGenerate.data.inputs.reduce((inputs, { nonWitnessUtxo, witnessUtxo }, inputIndex) => { + let inputData: PsbtTransactionArg | null = null; - if (existedInput === -1) { - inputs.push({ - address, + if (witnessUtxo) { + inputData = { + address: bitcoin.address.fromOutputScript(witnessUtxo?.script, network_), amount: witnessUtxo.value.toString() - }); - } else { - inputs[existedInput] = { - ...inputs[existedInput], - amount: new BigN(inputs[existedInput].amount || 0).plus(new BigN(witnessUtxo.value.toString())).toString() + }; + } else if (nonWitnessUtxo) { + const txin = psbtGenerate.txInputs[inputIndex]; + const txout = bitcoin.Transaction.fromBuffer(nonWitnessUtxo).outs[txin.index]; + + inputData = { + address: bitcoin.address.fromOutputScript(txout.script, network_), + amount: txout.value.toString() }; } + inputData && inputs.push(inputData); + return inputs; }, [] as PsbtTransactionArg[]); const psbtOutputData = psbtGenerate.txOutputs.map((output) => { - const address = output.address || bitcoin.address.fromOutputScript(output.script, network_); + let address = ''; + + try { + address = output.address || bitcoin.address.fromOutputScript(output.script, network_); + } catch (e) { + if (output.script.includes(bitcoin.opcodes.OP_RETURN)) { + address = 'OP_RETURN'; + } else { + address = 'Unknown'; + } + } if (isExistedInput(psbtInputData, address) === -1) { to = address; diff --git a/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts b/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts index 75b92b03481..461b62513b9 100644 --- a/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts +++ b/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { BitcoinProviderError } from '@subwallet/extension-base/background/errors/BitcoinProviderError'; -import { BitcoinProviderErrorType, ConfirmationDefinitionsBitcoin, ConfirmationsQueueBitcoin, ConfirmationsQueueItemOptions, ConfirmationTypeBitcoin, ExtrinsicDataTypeMap, RequestConfirmationCompleteBitcoin, SignMessageBitcoinResult, SignPsbtBitcoinResult } from '@subwallet/extension-base/background/KoniTypes'; +import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; +import { BasicTxErrorType, BitcoinProviderErrorType, ConfirmationDefinitionsBitcoin, ConfirmationsQueueBitcoin, ConfirmationsQueueItemOptions, ConfirmationTypeBitcoin, ExtrinsicDataTypeMap, RequestConfirmationCompleteBitcoin, SignMessageBitcoinResult, SignPsbtBitcoinResult } from '@subwallet/extension-base/background/KoniTypes'; import { ConfirmationRequestBase, Resolver } from '@subwallet/extension-base/background/types'; import { getBitcoinTransactionObject } from '@subwallet/extension-base/services/balance-service/helpers'; import { ChainService } from '@subwallet/extension-base/services/chain-service'; @@ -284,6 +285,13 @@ export default class BitcoinRequestHandler { // Extract necessary information from the BitcoinSendTransactionRequest const { account, payload } = request.payload; const { allowedSighash, broadcast, psbt, signAtIndex } = payload; + const transaction = this.#transactionService.getTransaction(request.id); + let eventData: TransactionEventResponse = { + id: request.id, + errors: [], + warnings: [], + extrinsicHash: request.id + }; // todo: validate type of the account @@ -299,8 +307,22 @@ export default class BitcoinRequestHandler { } const signAtIndexGenerate = signAtIndex ? (isArray(signAtIndex) ? signAtIndex : [signAtIndex]) : [...(Array(psbt.inputCount) as number[])].map((_, i) => i); + let psptSignedTransaction: Psbt | null = null; + // Sign the Psbt using the pair's bitcoin object - const psptSignedTransaction = pair.bitcoin.signTransaction(psbt, signAtIndexGenerate, allowedSighash); + try { + psptSignedTransaction = pair.bitcoin.signTransaction(psbt, signAtIndexGenerate, allowedSighash); + } catch (e) { + if (transaction) { + transaction.emitterTransaction?.emit('error', { ...eventData, errors: [new TransactionError(BasicTxErrorType.INVALID_PARAMS, (e as Error).message)], id: transaction.id, extrinsicHash: transaction.id }); + } + + throw new Error((e as Error).message); + } + + if (!psptSignedTransaction) { + throw new Error('Unable to sign'); + } if (!broadcast) { for (const index of signAtIndexGenerate) { @@ -312,12 +334,13 @@ export default class BitcoinRequestHandler { }; } - const transaction = this.#transactionService.getTransaction(request.id); + if (!transaction) { + throw new BitcoinProviderError(BitcoinProviderErrorType.INTERNAL_ERROR); + } - console.log(transaction); const { chain, emitterTransaction, id } = transaction; - const chainInfo = this.#chainService.getChainInfoByKey(chain); - const eventData: TransactionEventResponse = { + + eventData = { id, errors: [], warnings: [], @@ -328,7 +351,13 @@ export default class BitcoinRequestHandler { throw new BitcoinProviderError(BitcoinProviderErrorType.INTERNAL_ERROR); } - psptSignedTransaction.finalizeAllInputs(); + const chainInfo = this.#chainService.getChainInfoByKey(chain); + + try { + psptSignedTransaction.finalizeAllInputs(); + } catch (e) { + emitterTransaction.emit('error', { ...eventData, errors: [new TransactionError(BasicTxErrorType.INVALID_PARAMS, (e as Error).message)] }); + } const hexTransaction = psptSignedTransaction.extractTransaction().toHex(); @@ -336,11 +365,11 @@ export default class BitcoinRequestHandler { const { promise, reject, resolve } = createPromiseHandler(); emitterTransaction.on('extrinsicHash', (data) => { - if (!data.extrinsicHash) { + if (!data.extrinsicHash || !psptSignedTransaction) { reject(BitcoinProviderErrorType.INTERNAL_ERROR); } else { resolve({ - psbt: psptSignedTransaction.toHex(), + psbt: psptSignedTransaction?.toHex(), txid: data.extrinsicHash }); } diff --git a/packages/extension-base/src/services/transaction-service/index.ts b/packages/extension-base/src/services/transaction-service/index.ts index e26353d7bd5..c5d9e1c7693 100644 --- a/packages/extension-base/src/services/transaction-service/index.ts +++ b/packages/extension-base/src/services/transaction-service/index.ts @@ -371,6 +371,8 @@ export default class TransactionService { public async handleTransactionAfterConfirmation (transaction: SWTransactionInput): Promise { const validatedTransaction = await this.generalValidate(transaction); + + console.log(validatedTransaction, '123123'); const stopByErrors = validatedTransaction.errors.length > 0; const stopByWarnings = validatedTransaction.warnings.length > 0 && !validatedTransaction.ignoreWarnings; @@ -391,6 +393,8 @@ export default class TransactionService { // Fill transaction default info const transactionUpdated = this.fillTransactionDefaultInfo(validatedTransaction); + console.log(transactionUpdated, 'uopdate'); + // Add Transaction transactionsSubject[transactionUpdated.id] = { ...transactionUpdated, emitterTransaction: emitter }; this.transactionSubject.next({ ...transactionsSubject }); @@ -938,7 +942,10 @@ export default class TransactionService { const transaction = this.getTransaction(id); const nextStatus = ExtrinsicStatus.FAIL; + console.log('failed'); + if (transaction) { + console.log('failed', transaction); this.updateTransaction(id, { status: nextStatus, errors, extrinsicHash }); // Write failed transaction history diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx index 2265d7ce6d2..5cc4cbc9d9e 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx @@ -6,7 +6,7 @@ import { RequestSubmitTransferWithId } from '@subwallet/extension-base/types'; import { wait } from '@subwallet/extension-base/utils'; import { CONFIRMATION_QR_MODAL } from '@subwallet/extension-koni-ui/constants'; import { useGetChainInfoByChainId, useLedger, useNotification, useUnlockChecker } from '@subwallet/extension-koni-ui/hooks'; -import { completeConfirmationBitcoin, makePSBTTransferAfterConfirmation, makeTransferAfterConfirmation } from '@subwallet/extension-koni-ui/messaging'; +import { completeConfirmationBitcoin, makeBitcoinDappTransferConfirmation, makePSBTTransferAfterConfirmation } from '@subwallet/extension-koni-ui/messaging'; import { AccountSignMode, BitcoinSignatureSupportType, PhosphorIcon, SigData, ThemeProps } from '@subwallet/extension-koni-ui/types'; import { getSignMode, isBitcoinMessage, removeTransactionPersist } from '@subwallet/extension-koni-ui/utils'; import { Button, Icon, ModalContext } from '@subwallet/react-ui'; @@ -65,7 +65,6 @@ const Component: React.FC = (props: Props) => { const chain = useGetChainInfoByChainId(chainId); const checkUnlock = useUnlockChecker(); - const signMode = useMemo(() => getSignMode(account), [account]); const isLedger = useMemo(() => signMode === AccountSignMode.LEDGER, [signMode]); const isMessage = isBitcoinMessage(payload); @@ -113,7 +112,7 @@ const Component: React.FC = (props: Props) => { const promise = async () => { if (type === 'bitcoinSendTransactionRequestAfterConfirmation' && editedPayload) { - await makeTransferAfterConfirmation(editedPayload); + await makeBitcoinDappTransferConfirmation(editedPayload); } else if (type === 'bitcoinSignPsbtRequest') { const { payload: { account, broadcast, network, psbt, to, tokenSlug, txInput, txOutput, value } } = payload.payload as BitcoinSignPsbtRequest; @@ -133,8 +132,18 @@ const Component: React.FC = (props: Props) => { setLoading(false); }); }) - .catch(console.error); - }, [editedPayload, id, payload.payload, type]); + .catch((error) => { + console.error(error); + notify({ + message: t((error as Error).message), + type: 'error', + duration: 8 + }); + }) + .finally(() => { + setLoading(false); + }); + }, [editedPayload, id, notify, payload.payload, t, type]); const onApproveSignature = useCallback((signature: SigData) => { setLoading(true); diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx index 664ec0d7e59..ea51e7f878e 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/variants/BitcoinSignPsbtConfirmation.tsx @@ -31,7 +31,6 @@ function Component ({ className, request, type }: Props) { const accounts = useSelector((state: RootState) => state.accountState.accounts); const assetRegistry = useSelector((root: RootState) => root.assetRegistry.assetRegistry); const onClickDetail = useOpenDetailModal(); - const assetInfo: _ChainAsset | undefined = useMemo(() => { return assetRegistry[tokenSlug]; }, [assetRegistry, tokenSlug]); diff --git a/packages/extension-koni-ui/src/messaging/transaction/transfer.ts b/packages/extension-koni-ui/src/messaging/transaction/transfer.ts index 8f2176ae128..f326f4c42f3 100644 --- a/packages/extension-koni-ui/src/messaging/transaction/transfer.ts +++ b/packages/extension-koni-ui/src/messaging/transaction/transfer.ts @@ -11,12 +11,12 @@ export async function makeTransfer (request: RequestSubmitTransfer): Promise { - return sendMessage('pri(accounts.transfer.after.confirmation)', request); +export async function makeBitcoinDappTransferConfirmation (request: RequestSubmitTransferWithId): Promise { + return sendMessage('pri(accounts.bitcoin.dapp.transfer.confirmation)', request); } export async function makePSBTTransferAfterConfirmation (request: RequestSubmitSignPsbtTransfer): Promise { - return sendMessage('pri(accounts.psbt.transfer.after.confirmation)', request); + return sendMessage('pri(accounts.psbt.transfer.confirmation)', request); } export async function makeCrossChainTransfer (request: RequestCrossChainTransfer): Promise { From 9c1fc0788a4a0dffdc56f15346eff2b6873c74c1 Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Sat, 6 Jul 2024 18:19:28 +0700 Subject: [PATCH 11/11] [Issue 228] [fix] - eslint issue --- .../request-service/handler/BitcoinRequestHandler.ts | 1 + .../src/services/transaction-service/index.ts | 6 ------ .../src/Popup/Confirmations/parts/Sign/Bitcoin.tsx | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts b/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts index 461b62513b9..8964f698d67 100644 --- a/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts +++ b/packages/extension-base/src/services/request-service/handler/BitcoinRequestHandler.ts @@ -357,6 +357,7 @@ export default class BitcoinRequestHandler { psptSignedTransaction.finalizeAllInputs(); } catch (e) { emitterTransaction.emit('error', { ...eventData, errors: [new TransactionError(BasicTxErrorType.INVALID_PARAMS, (e as Error).message)] }); + throw new Error((e as Error).message); } const hexTransaction = psptSignedTransaction.extractTransaction().toHex(); diff --git a/packages/extension-base/src/services/transaction-service/index.ts b/packages/extension-base/src/services/transaction-service/index.ts index c5d9e1c7693..55911131b9a 100644 --- a/packages/extension-base/src/services/transaction-service/index.ts +++ b/packages/extension-base/src/services/transaction-service/index.ts @@ -372,7 +372,6 @@ export default class TransactionService { public async handleTransactionAfterConfirmation (transaction: SWTransactionInput): Promise { const validatedTransaction = await this.generalValidate(transaction); - console.log(validatedTransaction, '123123'); const stopByErrors = validatedTransaction.errors.length > 0; const stopByWarnings = validatedTransaction.warnings.length > 0 && !validatedTransaction.ignoreWarnings; @@ -393,8 +392,6 @@ export default class TransactionService { // Fill transaction default info const transactionUpdated = this.fillTransactionDefaultInfo(validatedTransaction); - console.log(transactionUpdated, 'uopdate'); - // Add Transaction transactionsSubject[transactionUpdated.id] = { ...transactionUpdated, emitterTransaction: emitter }; this.transactionSubject.next({ ...transactionsSubject }); @@ -942,10 +939,7 @@ export default class TransactionService { const transaction = this.getTransaction(id); const nextStatus = ExtrinsicStatus.FAIL; - console.log('failed'); - if (transaction) { - console.log('failed', transaction); this.updateTransaction(id, { status: nextStatus, errors, extrinsicHash }); // Write failed transaction history diff --git a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx index 5cc4cbc9d9e..8c21aa39dd0 100644 --- a/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx +++ b/packages/extension-koni-ui/src/Popup/Confirmations/parts/Sign/Bitcoin.tsx @@ -127,7 +127,6 @@ const Component: React.FC = (props: Props) => { }; promise().then(() => { - console.log('complete', type, id); handleConfirm(type, id, '').finally(() => { setLoading(false); });