From 89cda57483093303c8f571b17f5320f2ab6c85de Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Sat, 6 Jul 2024 06:12:11 +0700 Subject: [PATCH] [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 daf7e42fcc..ace0915a03 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 8317ba2994..8b9c40cbb5 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 2dc07b2ab6..0cf9f4fa7a 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 7d1ec9eb2e..75b92b0348 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 2638fddd59..e26353d7bd 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 3059821c93..ae38a49811 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 0f847bdd65..47726c097b 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 b5d24e758e..2265d7ce6d 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 ea4f8f6b36..664ec0d7e5 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 24397fde14..8f2176ae12 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); }