From 7fcb5c596e133ba3ae9749d12d08457b7c38c5e7 Mon Sep 17 00:00:00 2001 From: An Nguyen Date: Wed, 21 Aug 2024 16:03:19 -0500 Subject: [PATCH] CP-9009: Integrate with bitcoin_sendTransaction from bitcoin module (#1502) --- .../app/contexts/SendNFTContext.tsx | 5 + .../app/contexts/SendTokenContext.tsx | 6 +- .../hooks/browser/useInjectedJavascript.ts | 2 +- .../app/navigation/AppNavigation.ts | 1 - .../WalletScreenStack/WalletScreenStack.tsx | 2 - .../WalletScreenStack/createModals.tsx | 5 - packages/core-mobile/app/navigation/types.ts | 9 - .../bridge/hooks/useTransferAssetBTC.ts | 5 +- .../rpc/components/v2/ApprovalPopup.tsx | 53 ++- .../components/v2/BitcoinSendTransaction.tsx | 240 ----------- .../app/services/bridge/BridgeService.ts | 7 +- .../app/services/send/SendService.ts | 19 +- .../app/services/send/SendServiceBTC.ts | 7 +- .../walletconnectv2/WalletConnectService.ts | 92 +++-- .../services/walletconnectv2/utils.test.ts | 111 +++++- .../app/services/walletconnectv2/utils.ts | 76 +++- .../core-mobile/app/store/account/slice.ts | 14 +- .../avalanche_bridgeAsset.ts | 1 + .../bitcoin_sendTransaction.test.ts | 377 ------------------ .../bitcoin_sendTransaction.ts | 208 ---------- .../handlers/bitcoin_sendTransaction/utils.ts | 15 - .../app/store/rpc/handlers/index.ts | 4 +- .../handlers/wc_sessionRequest/namespaces.ts | 35 +- .../rpc/handlers/wc_sessionRequest/utils.ts | 17 +- .../wc_sessionRequest.test.ts | 82 +++- .../wc_sessionRequest/wc_sessionRequest.ts | 27 +- .../app/store/rpc/listeners/index.test.ts | 1 + .../providers/walletConnect/walletConnect.ts | 17 +- packages/core-mobile/app/store/rpc/types.ts | 13 +- .../getChainIdFromRequest.ts | 3 +- .../core-mobile/app/temp/caip2ChainIds.ts | 69 ++-- packages/core-mobile/app/utils/data/caip.ts | 2 +- .../app/utils/getJsonRpcErrorMessage.ts | 13 + .../ApprovalController/ApprovalController.ts | 24 +- .../core-mobile/app/vmModule/ModuleManager.ts | 7 +- .../avalancheSignTransaction.ts} | 2 +- .../vmModule/handlers/btcSendTransaction.ts | 67 ++++ .../ethSendTransaction.ts} | 11 +- .../signMessage.ts} | 10 +- packages/core-mobile/package.json | 22 +- yarn.lock | 170 ++++---- 41 files changed, 686 insertions(+), 1165 deletions(-) delete mode 100644 packages/core-mobile/app/screens/rpc/components/v2/BitcoinSendTransaction.tsx delete mode 100644 packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.test.ts delete mode 100644 packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.ts delete mode 100644 packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/utils.ts create mode 100644 packages/core-mobile/app/utils/getJsonRpcErrorMessage.ts rename packages/core-mobile/app/vmModule/{ApprovalController/handleAvalancheSignTransaction.ts => handlers/avalancheSignTransaction.ts} (98%) create mode 100644 packages/core-mobile/app/vmModule/handlers/btcSendTransaction.ts rename packages/core-mobile/app/vmModule/{ApprovalController/handleEthSendTransaction.ts => handlers/ethSendTransaction.ts} (81%) rename packages/core-mobile/app/vmModule/{ApprovalController/handleSignMessage.ts => handlers/signMessage.ts} (79%) diff --git a/packages/core-mobile/app/contexts/SendNFTContext.tsx b/packages/core-mobile/app/contexts/SendNFTContext.tsx index 2aae9bef7..eea1c1449 100644 --- a/packages/core-mobile/app/contexts/SendNFTContext.tsx +++ b/packages/core-mobile/app/contexts/SendNFTContext.tsx @@ -23,6 +23,8 @@ import { useNetworks } from 'hooks/networks/useNetworks' import { useNetworkFee } from 'hooks/useNetworkFee' import { useInAppRequest } from 'hooks/useInAppRequest' import { audioFeedback, Audios } from 'utils/AudioFeedback' +import { showTransactionErrorToast } from 'utils/toast' +import { getJsonRpcErrorMessage } from 'utils/getJsonRpcErrorMessage' export interface SendNFTContextState { sendToken: NFTItem @@ -132,6 +134,9 @@ export const SendNFTContextProvider = ({ }) .catch(reason => { setSendStatus('Fail') + showTransactionErrorToast({ + message: getJsonRpcErrorMessage(reason) + }) AnalyticsService.capture('NftSendFailed', { errorMessage: reason?.error?.message, chainId: activeNetwork.chainId diff --git a/packages/core-mobile/app/contexts/SendTokenContext.tsx b/packages/core-mobile/app/contexts/SendTokenContext.tsx index 1a1e5bb80..df1dbfa8a 100644 --- a/packages/core-mobile/app/contexts/SendTokenContext.tsx +++ b/packages/core-mobile/app/contexts/SendTokenContext.tsx @@ -26,6 +26,8 @@ import { useInAppRequest } from 'hooks/useInAppRequest' import { RootState } from 'store' import { audioFeedback, Audios } from 'utils/AudioFeedback' import { TokenWithBalance } from '@avalabs/vm-module-types' +import { showTransactionErrorToast } from 'utils/toast' +import { getJsonRpcErrorMessage } from 'utils/getJsonRpcErrorMessage' export type SendStatus = 'Idle' | 'Sending' | 'Success' | 'Fail' @@ -162,7 +164,9 @@ export const SendTokenContextProvider = ({ }) .catch(reason => { setSendStatus('Fail') - setError(reason.message) + showTransactionErrorToast({ + message: getJsonRpcErrorMessage(reason) + }) AnalyticsService.capture('SendTransactionFailed', { errorMessage: reason.message, chainId: activeNetwork.chainId diff --git a/packages/core-mobile/app/hooks/browser/useInjectedJavascript.ts b/packages/core-mobile/app/hooks/browser/useInjectedJavascript.ts index 91b17e5d4..fbab809dd 100644 --- a/packages/core-mobile/app/hooks/browser/useInjectedJavascript.ts +++ b/packages/core-mobile/app/hooks/browser/useInjectedJavascript.ts @@ -1,4 +1,4 @@ -import { BlockchainNamespace } from 'store/rpc/types' +import { BlockchainNamespace } from '@avalabs/core-chains-sdk' export type InjectedJavascripts = { injectCoreAsRecent: string diff --git a/packages/core-mobile/app/navigation/AppNavigation.ts b/packages/core-mobile/app/navigation/AppNavigation.ts index fcf2e2f04..6c59eb2e5 100644 --- a/packages/core-mobile/app/navigation/AppNavigation.ts +++ b/packages/core-mobile/app/navigation/AppNavigation.ts @@ -194,7 +194,6 @@ enum ModalScreens { EditSpendLimit = 'ModalScreens.EditSpendLimit', AvalancheSendTransactionV2 = 'ModalScreens.AvalancheSendTransactionV2', AvalancheSignTransactionV2 = 'ModalScreens.AvalancheSignTransactionV2', - BitcoinSendTransaction = 'ModalScreens.BitcoinSendTransaction', AvalancheSetDeveloperMode = 'ModalScreens.AvalancheSetDeveloperMode', StakeDisclaimer = 'ModalScreens.StakeDisclaimer', CoreIntro = 'ModalScreens.CoreIntro', diff --git a/packages/core-mobile/app/navigation/WalletScreenStack/WalletScreenStack.tsx b/packages/core-mobile/app/navigation/WalletScreenStack/WalletScreenStack.tsx index 5fee91a49..8a0dc8f29 100644 --- a/packages/core-mobile/app/navigation/WalletScreenStack/WalletScreenStack.tsx +++ b/packages/core-mobile/app/navigation/WalletScreenStack/WalletScreenStack.tsx @@ -82,7 +82,6 @@ import { ApprovalPopupParams, AvalancheSendTransactionV2Params, AvalancheSetDeveloperModeParams, - BitcoinSendTransactionParams, BridgeAssetV2Params, BridgeTransactionStatusParams, BuyCarefullyParams, @@ -180,7 +179,6 @@ export type WalletScreenStackParams = { [AppNavigation.Modal.BrowserTabCloseAll]: { onConfirm: () => void } [AppNavigation.Modal.AnalyticsConsentSheet]: undefined [AppNavigation.Modal.UseWalletConnect]: { onContinue: () => void } - [AppNavigation.Modal.BitcoinSendTransaction]: BitcoinSendTransactionParams [AppNavigation.Modal.AlertScreen]: AlertScreenParams } diff --git a/packages/core-mobile/app/navigation/WalletScreenStack/createModals.tsx b/packages/core-mobile/app/navigation/WalletScreenStack/createModals.tsx index 74b9ee6a2..1567cdcec 100644 --- a/packages/core-mobile/app/navigation/WalletScreenStack/createModals.tsx +++ b/packages/core-mobile/app/navigation/WalletScreenStack/createModals.tsx @@ -30,7 +30,6 @@ import { AreYouSureModal } from 'screens/browser/AreYouSureModal' import AnalyticsConsentSheet from 'screens/mainView/AnalyticsConsentSheet' import { AvalancheSetDeveloperMode } from 'screens/rpc/components/v2/AvalancheSetDeveloperMode' import { UseWalletConnectModal } from 'screens/browser/UseWalletConnectModal' -import BitcoinSendTransaction from 'screens/rpc/components/v2/BitcoinSendTransaction' import AlertScreen from 'screens/rpc/components/v2/AlertScreen' import EditSpendLimit from 'components/EditSpendLimit' import { SignOutModalScreen, WalletScreenSType } from './WalletScreenStack' @@ -80,10 +79,6 @@ export const createModals = (WalletScreenS: WalletScreenSType): JSX.Element => { name={AppNavigation.Modal.AvalancheSendTransactionV2} component={AvalancheSendTransactionV2} /> - { const caip2ChainId = request.chainId const chainId = getChainIdFromCaip2(caip2ChainId) const network = getNetwork(chainId) + const accountSelector = 'account' in signingData ? selectAccountByAddress(signingData.account) @@ -77,13 +79,11 @@ const ApprovalPopup = (): JSX.Element => { const approveDisabled = !network || !account || - (displayData.networkFeeSelector && !maxFeePerGas) || - (displayData.networkFeeSelector && !maxPriorityFeePerGas) || + (displayData.networkFeeSelector && maxFeePerGas === undefined) || + (displayData.networkFeeSelector && maxPriorityFeePerGas === undefined) || submitting - const showNetworkFeeSelector = - displayData.networkFeeSelector && - signingData.type === RpcMethod.ETH_SEND_TRANSACTION + const showNetworkFeeSelector = displayData.networkFeeSelector const rejectAndClose = useCallback( (message?: string) => { @@ -323,8 +323,8 @@ const ApprovalPopup = (): JSX.Element => { continue if (typeof value === 'string') { - const isAddress = value.substring(0, 2) === '0x' - + const isAddress = + isEvmAddress(value) || isBtcAddress(value, !network?.isTestnet) detailsToDisplay.push( {humanize(key)} @@ -420,6 +420,29 @@ const ApprovalPopup = (): JSX.Element => { return } + const renderNetworkFeeSelector = (): JSX.Element | null => { + if (!showNetworkFeeSelector || !chainId) return null + + let gasLimit: number | undefined + + if ( + typeof signingData.data === 'object' && + 'gasLimit' in signingData.data + ) { + gasLimit = Number(signingData.data.gasLimit || 0) + } + + if (!gasLimit) return null + + return ( + + ) + } + return ( <> { {renderSpendLimits()} {renderBalanceChange()} - {showNetworkFeeSelector && chainId && ( - - )} + {renderNetworkFeeSelector()} {renderDisclaimer()} {renderApproveRejectButtons()} diff --git a/packages/core-mobile/app/screens/rpc/components/v2/BitcoinSendTransaction.tsx b/packages/core-mobile/app/screens/rpc/components/v2/BitcoinSendTransaction.tsx deleted file mode 100644 index c3d872325..000000000 --- a/packages/core-mobile/app/screens/rpc/components/v2/BitcoinSendTransaction.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react' -import { Space } from 'components/Space' -import { ScrollView } from 'react-native-gesture-handler' -import { WalletScreenProps } from 'navigation/types' -import AppNavigation from 'navigation/AppNavigation' -import { useNavigation, useRoute } from '@react-navigation/native' -import { useDappConnectionV2 } from 'hooks/useDappConnectionV2' -import RpcRequestBottomSheet from 'screens/rpc/components/shared/RpcRequestBottomSheet' -import FeatureBlocked from 'screens/posthog/FeatureBlocked' -import { useSelector } from 'react-redux' -import { selectIsSeedlessSigningBlocked } from 'store/posthog' -import { selectAccounts, selectActiveAccount } from 'store/account/slice' -import { useApplicationContext } from 'contexts/ApplicationContext' -import { Row } from 'components/Row' -import { Eip1559Fees } from 'utils/Utils' -import SendRow from 'components/SendRow' -import { Text, View } from '@avalabs/k2-mobile' -import NetworkFeeSelector from 'components/NetworkFeeSelector' -import { NetworkTokenUnit } from 'types' -import { getBitcoinNetwork } from 'services/network/utils/providerUtils' -import { selectIsDeveloperMode } from 'store/settings/advanced' -import { TokenBaseUnit } from 'types/TokenBaseUnit' -import { selectTokensWithBalanceByNetwork } from 'store/balance/slice' -import { mustNumber } from 'utils/JsTools' -import { BitcoinSendTransactionApproveData } from 'store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction' -import { SendState } from 'services/send/types' -import { NetworkLogo } from 'screens/network/NetworkLogo' -import { selectBridgeCriticalConfig } from 'store/bridge' -import { isBtcBridge } from 'utils/isBtcBridge' - -type BitcoinSendTransactionScreenProps = WalletScreenProps< - typeof AppNavigation.Modal.BitcoinSendTransaction -> - -const BitcoinSendTransaction = (): JSX.Element => { - const isSeedlessSigningBlocked = useSelector(selectIsSeedlessSigningBlocked) - const activeAccount = useSelector(selectActiveAccount) - const { - appHook: { tokenInCurrencyFormatter } - } = useApplicationContext() - const { goBack } = - useNavigation() - const { request, data } = - useRoute().params - const { onUserApproved: onApprove, onUserRejected: onReject } = - useDappConnectionV2() - const isDeveloperMode = useSelector(selectIsDeveloperMode) - const btcNetwork = getBitcoinNetwork(isDeveloperMode) - const { sendState } = data - const bridgeConfig = useSelector(selectBridgeCriticalConfig) - const allAccounts = useSelector(selectAccounts) - const toAccountName = - Object.values(allAccounts).find(a => a.addressBTC === sendState.address) - ?.name ?? 'Address' - - const [maxFeePerGas, setMaxFeePerGas] = useState< - TokenBaseUnit - >(NetworkTokenUnit.fromNetwork(btcNetwork, sendState.defaultMaxFeePerGas)) - - const tokens = useSelector(selectTokensWithBalanceByNetwork(btcNetwork)) - - const btcToken = tokens.find(t => t.symbol === 'BTC') - const tokenPriceInSelectedCurrency = btcToken?.priceInCurrency ?? 0 - const amountInBtc = NetworkTokenUnit.fromNetwork(btcNetwork, sendState.amount) - const sendAmountInCurrency = amountInBtc.mul(tokenPriceInSelectedCurrency) - - const balanceAfterTrx = useMemo(() => { - let balanceBN = btcToken?.balance - if (activeAccount?.addressBTC !== sendState.address && balanceBN) { - balanceBN -= sendState.amount ?? 0n - } - return NetworkTokenUnit.fromNetwork(btcNetwork, balanceBN) - .sub(maxFeePerGas.mul(sendState.gasLimit ?? 0)) - .toFixed(4) - }, [ - activeAccount?.addressBTC, - btcNetwork, - btcToken?.balance, - maxFeePerGas, - sendState.address, - sendState.amount, - sendState.gasLimit - ]) - - const balanceAfterTrxInCurrency = useMemo( - () => - ( - tokenPriceInSelectedCurrency * - mustNumber(() => parseFloat(balanceAfterTrx), 0) - ).toFixed(2), - [balanceAfterTrx, tokenPriceInSelectedCurrency] - ) - - const rejectAndClose = useCallback(() => { - onReject(request) - goBack() - }, [goBack, onReject, request]) - - const onHandleApprove = (): void => { - onApprove(request, { - sendState: { - ...sendState, - defaultMaxFeePerGas: maxFeePerGas.toSubUnit() - } as SendState - } as BitcoinSendTransactionApproveData) - goBack() - } - - const handleFeeChange = useCallback( - (fees: Eip1559Fees) => { - setMaxFeePerGas(fees.maxFeePerGas) - }, - [setMaxFeePerGas] - ) - - const title = useMemo(() => { - return sendState.address && isBtcBridge(sendState.address, bridgeConfig) - ? 'Confirm Bridge' - : 'Approve Transaction' - }, [bridgeConfig, sendState.address]) - - const renderNetwork = (): JSX.Element | undefined => { - return ( - - - Network: - - - {btcNetwork.chainName} - - - - - ) - } - - return ( - <> - - - - {title} - - {renderNetwork()} - - - Amount - - - {Number(amountInBtc)} - - - {'BTC'} - - - - - - {tokenInCurrencyFormatter(sendAmountInCurrency.toDisplay())} - - - - - - - - - - Balance After Transaction - - - {balanceAfterTrx} {btcToken?.symbol ?? ''} - - - - {tokenInCurrencyFormatter(balanceAfterTrxInCurrency)} - - - - - {isSeedlessSigningBlocked && ( - - )} - - ) -} - -export default BitcoinSendTransaction diff --git a/packages/core-mobile/app/services/bridge/BridgeService.ts b/packages/core-mobile/app/services/bridge/BridgeService.ts index 4d210c89b..65d5f5167 100644 --- a/packages/core-mobile/app/services/bridge/BridgeService.ts +++ b/packages/core-mobile/app/services/bridge/BridgeService.ts @@ -30,12 +30,13 @@ import { Request } from 'store/rpc/utils/createInAppRequest' import { RpcMethod } from 'store/rpc/types' import { bnToBig, noop, stringToBN } from '@avalabs/core-utils-sdk' import { transactionRequestToTransactionParams } from 'store/rpc/utils/transactionRequestToTransactionParams' -import { getEvmCaip2ChainId } from 'temp/caip2ChainIds' +import { getBitcoinCaip2ChainId, getEvmCaip2ChainId } from 'temp/caip2ChainIds' type TransferBTCParams = { amount: string feeRate: number config: AppConfig + isMainnet: boolean onStatusChange?: (status: WrapStatus) => void onTxHashChange?: (txHash: string) => void request: Request @@ -141,6 +142,7 @@ export class BridgeService { amount, config, feeRate, + isMainnet, onStatusChange, onTxHashChange, request @@ -149,7 +151,8 @@ export class BridgeService { async txData => { return request({ method: RpcMethod.BITCOIN_SEND_TRANSACTION, - params: txData + params: txData, + chainId: getBitcoinCaip2ChainId(isMainnet) }) } diff --git a/packages/core-mobile/app/services/send/SendService.ts b/packages/core-mobile/app/services/send/SendService.ts index d8ec87716..8a0361a7c 100644 --- a/packages/core-mobile/app/services/send/SendService.ts +++ b/packages/core-mobile/app/services/send/SendService.ts @@ -1,3 +1,4 @@ +import { BitcoinSendTransactionParams } from '@avalabs/bitcoin-module' import { Network, NetworkVMType } from '@avalabs/core-chains-sdk' import { resolve } from '@avalabs/core-utils-sdk' import { Account } from 'store/account' @@ -8,13 +9,13 @@ import { isErc721 } from 'services/nft/utils' import { SendServicePVM } from 'services/send/SendServicePVM' import { RpcMethod } from 'store/rpc' import { getAddressByNetwork } from 'store/account/utils' -import { TransactionParams as BtcTransactionParams } from 'store/rpc/handlers/bitcoin_sendTransaction/utils' import { TransactionParams as AvalancheTransactionParams } from 'store/rpc/handlers/avalanche_sendTransaction/utils' import { SendServiceAVM } from 'services/send/SendServiceAVM' import { transactionRequestToTransactionParams } from 'store/rpc/utils/transactionRequestToTransactionParams' import { type NftTokenWithBalance, TokenType } from '@avalabs/vm-module-types' import { getAvalancheCaip2ChainId, + getBitcoinCaip2ChainId, getEvmCaip2ChainId } from 'temp/caip2ChainIds' import sendServiceBTC from './SendServiceBTC' @@ -55,23 +56,25 @@ class SendService { throw new Error(sendState.error.message) } - if (!isValidSendState(sendState)) { + if (!isValidSendState(sendState) || !sendState.address) { throw new Error('Unknown error, unable to submit') } let txHash, txError if (network.vmName === NetworkVMType.BITCOIN) { - const params: BtcTransactionParams = [ - sendState.address ?? '', - sendState.amount?.toString() ?? '', - Number(sendState.defaultMaxFeePerGas ?? 0) - ] + const params: BitcoinSendTransactionParams = { + from: fromAddress, + to: sendState.address, + amount: Number(sendState.amount), + feeRate: Number(sendState.defaultMaxFeePerGas ?? 0) + } ;[txHash, txError] = await resolve( request({ method: RpcMethod.BITCOIN_SEND_TRANSACTION, - params + params, + chainId: getBitcoinCaip2ChainId(!network.isTestnet) }) ) } diff --git a/packages/core-mobile/app/services/send/SendServiceBTC.ts b/packages/core-mobile/app/services/send/SendServiceBTC.ts index c042b7c7a..94704a14f 100644 --- a/packages/core-mobile/app/services/send/SendServiceBTC.ts +++ b/packages/core-mobile/app/services/send/SendServiceBTC.ts @@ -19,6 +19,7 @@ import { Transaction } from '@sentry/types' import { isBtcAddress } from 'utils/isBtcAddress' import ModuleManager from 'vmModule/ModuleManager' import { mapToVmNetwork } from 'vmModule/utils/mapToVmNetwork' +import { calculateGasLimit } from '@avalabs/bitcoin-module' class SendServiceBTC implements SendServiceHelper { async getTransactionRequest( @@ -126,11 +127,7 @@ class SendServiceBTC implements SendServiceHelper { error: undefined, maxAmount, sendFee: BigInt(fee), - // The transaction's byte size is for BTC as gasLimit is for EVM. - // Bitcoin's formula for fee is `transactionByteLength * feeRate`. - // Since we know the `fee` and the `feeRate`, we can get the transaction's - // byte length by division. - gasLimit: fee / feeRate + gasLimit: calculateGasLimit(fee, feeRate) } if (!amountInSatoshis) diff --git a/packages/core-mobile/app/services/walletconnectv2/WalletConnectService.ts b/packages/core-mobile/app/services/walletconnectv2/WalletConnectService.ts index 14a0e443e..1e058b2c5 100644 --- a/packages/core-mobile/app/services/walletconnectv2/WalletConnectService.ts +++ b/packages/core-mobile/app/services/walletconnectv2/WalletConnectService.ts @@ -4,19 +4,23 @@ import { IWeb3Wallet, Web3Wallet } from '@walletconnect/web3wallet' import { getSdkError } from '@walletconnect/utils' import Config from 'react-native-config' import { RpcError } from '@avalabs/vm-module-types' +import { BlockchainNamespace } from '@avalabs/core-chains-sdk' import { assertNotUndefined } from 'utils/assertions' import Logger from 'utils/Logger' import promiseWithTimeout from 'utils/js/promiseWithTimeout' -import { isBitcoinChainId } from 'utils/network/isBitcoinNetwork' import { WalletConnectServiceNoop } from 'services/walletconnectv2/WalletConnectServiceNoop' import { CorePrimaryAccount } from '@avalabs/types' -import { isPChain, isXChain } from 'utils/network/isAvalancheNetwork' import { CLIENT_METADATA, WalletConnectCallbacks, WalletConnectServiceInterface } from './types' -import { addNamespaceToAddress, addNamespaceToChain } from './utils' +import { + addNamespaceToChain, + getAddressWithCaip2ChainId, + updateAccountListInNamespace, + updateChainListInNamespace +} from './utils' const UPDATE_SESSION_TIMEOUT = 15000 @@ -196,45 +200,48 @@ class WalletConnectService implements WalletConnectServiceInterface { chainId: number account: CorePrimaryAccount }): Promise => { - if (isBitcoinChainId(chainId)) { - Logger.info('skip updating WC session for bitcoin network') - return + const topic = session.topic + const caip2ChainId = addNamespaceToChain(chainId) + + const blockchainNamespace = caip2ChainId.split(':')[0] + + if (!blockchainNamespace) { + throw new Error('invalid chain data') + } + + const addressWithCaip2ChainId = getAddressWithCaip2ChainId({ + account, + blockchainNamespace, + caip2ChainId + }) + + if (!addressWithCaip2ChainId) { + throw new Error('invalid chain data') } Logger.info( - `updating WC session '${session.peer.metadata.name}' with chainId ${chainId} and address ${account.addressC}` + `updating WC session '${session.peer.metadata.name}' with chainId '${caip2ChainId}' and account '${addressWithCaip2ChainId}'` ) - const topic = session.topic - const formattedChain = addNamespaceToChain(chainId) - let address = account.addressC - if (isXChain(chainId)) { - address = account.addressAVM - } else if (isPChain(chainId)) { - address = account.addressPVM - } - const formattedAddress = addNamespaceToAddress(address, chainId) const namespaces: SessionTypes.Namespaces = {} for (const key of Object.keys(session.namespaces)) { const namespace = session.namespaces[key] - if (namespace) { - const chains = namespace.chains || [] - if (!chains.includes(formattedChain)) { - chains.push(formattedChain) - } - - const accounts = namespace.accounts - if (!accounts.includes(formattedAddress)) { - accounts.push(formattedAddress) - } - - namespaces[key] = { - ...namespace, - chains, - accounts - } + + if (!namespace) continue + + // for the matching namespace, we need to update both chain and account lists + // for the rest, we just leave as is + if (key === blockchainNamespace) { + updateChainListInNamespace({ chains: namespace.chains, caip2ChainId }) + + updateAccountListInNamespace({ + account: addressWithCaip2ChainId, + accounts: namespace.accounts + }) } + + namespaces[key] = { ...namespace } } // check if dapp is online first @@ -246,22 +253,30 @@ class WalletConnectService implements WalletConnectServiceInterface { }) // emitting events + // but only for evm chains since neither wagmi/universal provider can handle non-evm chain events + if (blockchainNamespace !== BlockchainNamespace.EIP155) { + Logger.info( + 'skipping emitting wallet connect events since it is for a non-evm chain' + ) + return + } + await this.client.emitSessionEvent({ topic, event: { name: 'chainChanged', - data: formattedChain + data: chainId }, - chainId: formattedChain + chainId: caip2ChainId }) await this.client.emitSessionEvent({ topic, event: { name: 'accountsChanged', - data: [formattedAddress] + data: [addressWithCaip2ChainId] }, - chainId: formattedChain + chainId: caip2ChainId }) } @@ -294,11 +309,6 @@ class WalletConnectService implements WalletConnectServiceInterface { chainId: number account: CorePrimaryAccount }): Promise => { - if (isBitcoinChainId(chainId)) { - Logger.info('skip updating WC sessions for bitcoin network') - return - } - const promises: Promise[] = [] this.getSessions().forEach(session => { diff --git a/packages/core-mobile/app/services/walletconnectv2/utils.test.ts b/packages/core-mobile/app/services/walletconnectv2/utils.test.ts index 232115ea6..810d74157 100644 --- a/packages/core-mobile/app/services/walletconnectv2/utils.test.ts +++ b/packages/core-mobile/app/services/walletconnectv2/utils.test.ts @@ -1,4 +1,27 @@ -import { addNamespaceToChain, addNamespaceToAddress } from './utils' +import { CorePrimaryAccount, CoreAccountType, WalletType } from '@avalabs/types' +import { + BlockchainNamespace, + AvalancheCaip2ChainId, + BitcoinCaip2ChainId +} from '@avalabs/core-chains-sdk' +import { addNamespaceToChain, getAddressWithCaip2ChainId } from './utils' + +// Mock data +const mockAccount: CorePrimaryAccount = { + active: true, + name: 'aaaa', + id: '1', + index: 0, + type: CoreAccountType.PRIMARY, + walletType: WalletType.Mnemonic, + walletId: 'walletId', + walletName: 'walletName', + addressAVM: 'AVMAddress', + addressPVM: 'PVMAddress', + addressBTC: 'BTCAddress', + addressC: 'CAddress', + addressCoreEth: 'CoreEthAddress' +} describe('addNamespaceToChain', () => { it('should add eip155 namespace to a chainId', () => { @@ -8,11 +31,85 @@ describe('addNamespaceToChain', () => { }) }) -describe('addNamespaceToAddress', () => { - it('should add eip155 and chainId to an address', () => { - const address = '0x1234567890abcdef' - const chainId = 1 - const result = addNamespaceToAddress(address, chainId) - expect(result).toBe('eip155:1:0x1234567890abcdef') +describe('getAddressWithCaip2ChainId', () => { + it('should return AVM address for AVAX namespace with X chain ID', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.AVAX, + caip2ChainId: AvalancheCaip2ChainId.X + }) + expect(result).toBe('avax:imji8papUf2EhV3le337w1vgFauqkJg-:AVMAddress') + }) + + it('should return AVM address for AVAX namespace with X testnet chain ID', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.AVAX, + caip2ChainId: AvalancheCaip2ChainId.X_TESTNET + }) + expect(result).toBe('avax:8AJTpRj3SAqv1e80Mtl9em08LhvKEbkl:AVMAddress') + }) + + it('should return PVM address for AVAX namespace with P chain ID', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.AVAX, + caip2ChainId: AvalancheCaip2ChainId.P + }) + expect(result).toBe('avax:Rr9hnPVPxuUvrdCul-vjEsU1zmqKqRDo:PVMAddress') + }) + + it('should return PVM address for AVAX namespace with P testnet chain ID', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.AVAX, + caip2ChainId: AvalancheCaip2ChainId.P_TESTNET + }) + expect(result).toBe('avax:Sj7NVE3jXTbJvwFAiu7OEUo_8g8ctXMG:PVMAddress') + }) + + it('should return BTC address for BIP122 namespace with mainnet chain ID', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.BIP122, + caip2ChainId: BitcoinCaip2ChainId.MAINNET + }) + expect(result).toBe('bip122:000000000019d6689c085ae165831e93:BTCAddress') + }) + + it('should return BTC address for BIP122 namespace with testnet chain ID', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.BIP122, + caip2ChainId: BitcoinCaip2ChainId.TESTNET + }) + expect(result).toBe('bip122:000000000933ea01ad0ee984209779ba:BTCAddress') + }) + + it('should return C address for EIP155 namespace', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.EIP155, + caip2ChainId: 'eip155:1' + }) + expect(result).toBe('eip155:1:CAddress') + }) + + it('should return undefined for unknown namespace', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: 'UNKNOWN', + caip2ChainId: 'unknown:1' + }) + expect(result).toBeUndefined() + }) + + it('should return undefined for AVAX namespace with invalid chain ID', () => { + const result = getAddressWithCaip2ChainId({ + account: mockAccount, + blockchainNamespace: BlockchainNamespace.AVAX, + caip2ChainId: 'invalid:1' + }) + expect(result).toBeUndefined() }) }) diff --git a/packages/core-mobile/app/services/walletconnectv2/utils.ts b/packages/core-mobile/app/services/walletconnectv2/utils.ts index 8cb9b03a8..60ea84150 100644 --- a/packages/core-mobile/app/services/walletconnectv2/utils.ts +++ b/packages/core-mobile/app/services/walletconnectv2/utils.ts @@ -1,10 +1,19 @@ -import { BlockchainNamespace } from 'store/rpc/types' -import { getAvalancheCaip2ChainId } from 'temp/caip2ChainIds' +import { BlockchainNamespace } from '@avalabs/core-chains-sdk' +import { + getAvalancheCaip2ChainId, + getBitcoinCaip2ChainIdByChainId, + isAVMChainId, + isPVMChainId +} from 'temp/caip2ChainIds' +import { CorePrimaryAccount } from '@avalabs/types' -// prefix eip155 namespace to a chainId +// prefix correct namespace to a chainId // '1' -> 'eip155:1' export const addNamespaceToChain = (chainId: number): string => { - const caip2ChainId = getAvalancheCaip2ChainId(chainId) + const caip2ChainId = + getAvalancheCaip2ChainId(chainId) || + getBitcoinCaip2ChainIdByChainId(chainId) + if (caip2ChainId) { return caip2ChainId } @@ -12,16 +21,55 @@ export const addNamespaceToChain = (chainId: number): string => { return `${BlockchainNamespace.EIP155}:${chainId}` } -// prefix eip155 namespace and chainId to an address -// '0x241b0073b66bfc19FCB54308861f604F5Eb8f51b' -> 'eip155:1:0x241b0073b66bfc19FCB54308861f604F5Eb8f51b' -export const addNamespaceToAddress = ( - address: string, - chainId: number -): string => { - const caip2ChainId = getAvalancheCaip2ChainId(chainId) - if (caip2ChainId) { - return `${caip2ChainId}:${address}` +// generate full address with caip2 chain ID based on the blockchain namespace +// an example result 'eip155:1:0x241b0073b66bfc19FCB54308861f604F5Eb8f51b' +export const getAddressWithCaip2ChainId = ({ + account, + blockchainNamespace, + caip2ChainId +}: { + account: CorePrimaryAccount + blockchainNamespace: string + caip2ChainId: string +}): string | undefined => { + let address: string | undefined + + if (blockchainNamespace === BlockchainNamespace.AVAX) { + address = isAVMChainId(caip2ChainId) + ? `${caip2ChainId}:${account.addressAVM}` + : isPVMChainId(caip2ChainId) + ? `${caip2ChainId}:${account.addressPVM}` + : undefined + } else if (blockchainNamespace === BlockchainNamespace.BIP122) { + address = `${caip2ChainId}:${account.addressBTC}` + } else if (blockchainNamespace === BlockchainNamespace.EIP155) { + address = `${caip2ChainId}:${account.addressC}` } - return `${BlockchainNamespace.EIP155}:${chainId}:${address}` + return address +} + +export const updateAccountListInNamespace = ({ + account, + accounts +}: { + account: string + accounts: string[] +}): void => { + if (!accounts.includes(account)) { + accounts.push(account) + } +} + +export const updateChainListInNamespace = ({ + chains, + caip2ChainId +}: { + chains: string[] | undefined + caip2ChainId: string +}): void => { + const existingChains = chains || [] + if (!existingChains.includes(caip2ChainId)) { + existingChains.push(caip2ChainId) + } } diff --git a/packages/core-mobile/app/store/account/slice.ts b/packages/core-mobile/app/store/account/slice.ts index 257b00af6..286669fdb 100644 --- a/packages/core-mobile/app/store/account/slice.ts +++ b/packages/core-mobile/app/store/account/slice.ts @@ -60,13 +60,19 @@ export const selectAccounts = (state: RootState): AccountCollection => state.account.accounts export const selectAccountByAddress = - (address?: string) => + (address: string) => (state: RootState): Account | undefined => { const accounts: Account[] = Object.values(state.account.accounts) + const givenAddress = address.toLowerCase() - return accounts.find( - acc => acc.addressC.toLowerCase() === address?.toLowerCase() - ) + return accounts.find(acc => { + return ( + acc.addressC.toLowerCase() === givenAddress || + acc.addressBTC.toLowerCase() === givenAddress || + acc.addressAVM.toLowerCase() === givenAddress || + acc.addressPVM.toLowerCase() === givenAddress + ) + }) } export const selectAccountByIndex = diff --git a/packages/core-mobile/app/store/rpc/handlers/avalanche_bridgeAsset/avalanche_bridgeAsset.ts b/packages/core-mobile/app/store/rpc/handlers/avalanche_bridgeAsset/avalanche_bridgeAsset.ts index 710eed145..652a738e0 100644 --- a/packages/core-mobile/app/store/rpc/handlers/avalanche_bridgeAsset/avalanche_bridgeAsset.ts +++ b/packages/core-mobile/app/store/rpc/handlers/avalanche_bridgeAsset/avalanche_bridgeAsset.ts @@ -110,6 +110,7 @@ class AvalancheBridgeAssetHandler amount: amountStr, config: bridgeAppConfig, feeRate: Number(maxFeePerGas), + isMainnet: !isDeveloperMode, onStatusChange: noop, onTxHashChange: noop, request diff --git a/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.test.ts b/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.test.ts deleted file mode 100644 index f86c226f5..000000000 --- a/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.test.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { rpcErrors } from '@metamask/rpc-errors' -import { RpcMethod, RpcProvider, RpcRequest } from 'store/rpc/types' -import mockSession from 'tests/fixtures/walletConnect/session.json' -import mockAccounts from 'tests/fixtures/accounts.json' -import mockNetworks from 'tests/fixtures/networks.json' -import * as Navigation from 'utils/Navigation' -import * as Sentry from '@sentry/react-native' -import WalletService from 'services/wallet/WalletService' -import NetworkService from 'services/network/NetworkService' -import { selectNetwork } from 'store/network' -import { VsCurrencyType } from '@avalabs/core-coingecko-sdk' -import * as utils from 'utils/isBtcAddress' -import * as accountStore from 'store/account/slice' -import SendServiceBTC from 'services/send/SendServiceBTC' -import * as Toast from 'utils/toast' -import BalanceService from 'services/balance/BalanceService' -import { DEFERRED_RESULT } from '../types' -import { bitcoinSendTransactionHandler as handler } from './bitcoin_sendTransaction' - -const mockShowTransactionSuccessToast = jest.fn() -const mockShowTransactionErrorToast = jest.fn() -jest - .spyOn(Toast, 'showTransactionSuccessToast') - .mockImplementation(mockShowTransactionSuccessToast) -jest - .spyOn(Toast, 'showTransactionErrorToast') - .mockImplementation(mockShowTransactionErrorToast) - -const mockIsDeveloperMode = true -jest.mock('store/settings/advanced', () => { - const actual = jest.requireActual('store/settings/advanced') - return { - ...actual, - selectIsDeveloperMode: () => mockIsDeveloperMode - } -}) - -const mockGetBalances = jest.fn() -jest - .spyOn(BalanceService, 'getBalancesForAccount') - .mockImplementation(mockGetBalances) - -const mockValidateStateAndCalculateFees = jest.fn() -jest - .spyOn(SendServiceBTC, 'validateStateAndCalculateFees') - .mockImplementation(mockValidateStateAndCalculateFees) - -const mockGetTransactionRequest = jest.fn() -jest - .spyOn(SendServiceBTC, 'getTransactionRequest') - .mockImplementation(mockGetTransactionRequest) - -const mockSign = jest.fn() -jest.spyOn(WalletService, 'sign').mockImplementation(mockSign) - -const mockSendTransaction = jest.fn() -jest - .spyOn(NetworkService, 'sendTransaction') - .mockImplementation(mockSendTransaction) - -jest.mock('hooks/useNetworkFee', () => { - const actual = jest.requireActual('hooks/useNetworkFee') - return { - ...actual, - prefetchNetworkFee: jest.fn() - } -}) - -const mockNetwork = mockNetworks[43114] -jest.mock('store/network', () => { - const actual = jest.requireActual('store/network') - return { - ...actual, - selectNetwork: jest.fn() - } -}) -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockSelectNetwork = selectNetwork as jest.MockedFunction -mockSelectNetwork.mockImplementation(() => mockNetwork) - -const mockAccount = jest.fn() -const mockSelectAccountByAddress = jest.fn() -jest - .spyOn(accountStore, 'selectAccountByAddress') - .mockImplementation(mockSelectAccountByAddress) -jest.spyOn(accountStore, 'selectActiveAccount').mockImplementation(mockAccount) - -jest.mock('store/settings/currency', () => { - const actual = jest.requireActual('store/settings/currency') - return { - ...actual, - selectSelectedCurrency: () => 'usd' as VsCurrencyType - } -}) - -const mockCaptureException = jest.fn() -jest.spyOn(Sentry, 'captureException').mockImplementation(mockCaptureException) - -const mockNavigate = jest.fn() -jest.spyOn(Navigation, 'navigate').mockImplementation(mockNavigate) - -const mockIsBtcAddress = jest.fn().mockReturnValue(true) -jest.spyOn(utils, 'isBtcAddress').mockImplementation(mockIsBtcAddress) - -const mockDispatch = jest.fn() -const mockListenerApi = { - getState: jest.fn(), - dispatch: mockDispatch - // eslint-disable-next-line @typescript-eslint/no-explicit-any -} as any - -const testMethod = RpcMethod.BITCOIN_SEND_TRANSACTION - -const testParams = ['0xcA0E993876152ccA6053eeDFC753092c8cE712D0', '100', 1] -const createRequest = ( - params: unknown -): RpcRequest => { - return { - provider: RpcProvider.WALLET_CONNECT, - method: testMethod, - data: { - id: 1677366383831712, - topic: '3a094bf511357e0f48ff266f0b8d5b846fd3f7de4bd0824d976fdf4c5279b261', - params: { - request: { - method: testMethod, - params - }, - chainId: 'eip155:43113' - } - }, - peerMeta: mockSession.peer.metadata - } -} - -const testHandleInvalidParams = async (params: unknown) => { - const testRequest = createRequest(params) - - const result = await handler.handle(testRequest, mockListenerApi) - - expect(result).toEqual({ - success: false, - error: rpcErrors.invalidParams('Missing mandatory param(s)') - }) -} - -const approvedTestData = { - sendState: { - to: '0xcA0E993876', - value: '100', - fee: '1', - nonce: 1, - canSubmit: true - } -} -describe('bitcoin_sendTransaction', () => { - describe('handler', () => { - beforeEach(() => { - jest.clearAllMocks() - mockValidateStateAndCalculateFees.mockReturnValue({ - to: '0xcA0E993876', - value: '100', - fee: '1', - nonce: 1, - canSubmit: true - }) - mockGetBalances.mockReturnValue([ - { - balance: '100', - availableBalance: '100', - lockedBalance: '0' - } - ]) - mockAccount.mockReturnValue(mockAccounts[0]) - mockSelectAccountByAddress.mockReturnValue(mockAccount) - mockIsBtcAddress.mockReturnValue(true) - }) - - it('should contain correct methods', () => { - expect(handler.methods).toEqual(['bitcoin_sendTransaction']) - }) - - it('should navigate with approved data', async () => { - const testRequest = createRequest(testParams) - const result = await handler.handle(testRequest, mockListenerApi) - expect(result).toEqual({ - success: true, - value: DEFERRED_RESULT - }) - expect(mockNavigate).toHaveBeenCalledWith({ - name: 'Root.Wallet', - params: { - params: { - data: approvedTestData, - request: testRequest - }, - screen: 'ModalScreens.BitcoinSendTransaction' - } - }) - }) - - it('should return error when params are invalid', async () => { - const invalidParamsScenarios = [ - null, - [], - [null], - ['0xcA0E993876152ccA6053eeDFC753092c8cE712D0'], - ['0xcA0E993876152ccA6053eeDFC753092c8cE712D0', '100'], - [1] - ] - - for (const scenario of invalidParamsScenarios) { - await testHandleInvalidParams(scenario) - } - }) - - it('should return error if address is not btc address', async () => { - mockIsBtcAddress.mockReturnValue(false) - const testRequest = createRequest(testParams) - const result = await handler.handle(testRequest, mockListenerApi) - expect(result).toEqual({ - success: false, - error: rpcErrors.internal('Not a valid address.') - }) - }) - - it('should return error if no active account', async () => { - mockAccount.mockReturnValue(undefined) - const testRequest = createRequest(testParams) - const result = await handler.handle(testRequest, mockListenerApi) - expect(result).toEqual({ - success: false, - error: rpcErrors.internal('No active account found') - }) - }) - it('should return error if no btc address', async () => { - mockAccount.mockReturnValue({ ...mockAccount(), addressBTC: undefined }) - const testRequest = createRequest(testParams) - const result = await handler.handle(testRequest, mockListenerApi) - expect(result).toEqual({ - success: false, - error: rpcErrors.internal( - 'The active account does not support BTC transactions' - ) - }) - }) - - it('should return error if sendState has error ', async () => { - mockValidateStateAndCalculateFees.mockReturnValue({ - error: { - error: 'error' - } - }) - const testRequest = createRequest(testParams) - const result = await handler.handle(testRequest, mockListenerApi) - expect(result).toEqual({ - success: false, - error: rpcErrors.internal('Transaction rejected.') - }) - }) - it('should return error if canSubmit is false ', async () => { - mockValidateStateAndCalculateFees.mockReturnValue({ - canSubmit: false - }) - const testRequest = createRequest(testParams) - const result = await handler.handle(testRequest, mockListenerApi) - expect(result).toEqual({ - success: false, - error: rpcErrors.internal('Unable to construct the transaction.') - }) - }) - }) - - describe('approve', () => { - beforeEach(() => { - jest.clearAllMocks() - mockSendTransaction.mockResolvedValue('0x123') - mockAccount.mockReturnValue(mockAccounts[0]) - }) - it('should return hash when transaction is signed and sent', async () => { - const testRequest = createRequest(testParams) - const result = await handler.approve( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { request: testRequest, data: approvedTestData as any }, - mockListenerApi - ) - - expect(mockShowTransactionSuccessToast).toHaveBeenCalledWith({ - message: 'Transaction Successful', - txHash: '0x123' - }) - - expect(result).toEqual({ - success: true, - value: '0x123' - }) - }) - - it('should return error when active account has no btc address', async () => { - mockAccount.mockReturnValue({ ...mockAccount(), addressBTC: undefined }) - const testRequest = createRequest(testParams) - const result = await handler.approve( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { request: testRequest, data: approvedTestData as any }, - mockListenerApi - ) - - expect(mockShowTransactionErrorToast).toHaveBeenCalledWith({ - message: 'Transaction Failed' - }) - - expect(result).toEqual({ - success: false, - error: rpcErrors.internal( - 'The active account does not support BTC transactions' - ) - }) - }) - - it('should return error when getTransactionRequest fails', async () => { - mockGetTransactionRequest.mockRejectedValueOnce(new Error('error')) - const testRequest = createRequest(testParams) - const result = await handler.approve( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { request: testRequest, data: approvedTestData as any }, - mockListenerApi - ) - - expect(mockShowTransactionErrorToast).toHaveBeenCalledWith({ - message: 'Transaction Failed' - }) - - expect(result).toEqual({ - success: false, - error: rpcErrors.internal('error') - }) - }) - - it('should return error when sign transaction fails', async () => { - mockSign.mockRejectedValueOnce(new Error('error')) - const testRequest = createRequest(testParams) - const result = await handler.approve( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { request: testRequest, data: approvedTestData as any }, - mockListenerApi - ) - - expect(mockShowTransactionErrorToast).toHaveBeenCalledWith({ - message: 'Transaction Failed' - }) - - expect(result).toEqual({ - success: false, - error: rpcErrors.internal('error') - }) - }) - - it('should return error when send transaction fails', async () => { - mockSendTransaction.mockRejectedValueOnce(new Error('error')) - const testRequest = createRequest(testParams) - const result = await handler.approve( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { request: testRequest, data: approvedTestData as any }, - mockListenerApi - ) - - expect(mockShowTransactionErrorToast).toHaveBeenCalledWith({ - message: 'Transaction Failed' - }) - - expect(result).toEqual({ - success: false, - error: rpcErrors.internal('error') - }) - }) - }) -}) diff --git a/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.ts b/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.ts deleted file mode 100644 index d6c79e9b6..000000000 --- a/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/bitcoin_sendTransaction.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { AppListenerEffectAPI } from 'store' -import * as Navigation from 'utils/Navigation' -import AppNavigation from 'navigation/AppNavigation' -import { rpcErrors } from '@metamask/rpc-errors' -import { selectActiveAccount } from 'store/account' -import { selectIsDeveloperMode } from 'store/settings/advanced' -import { RpcMethod, RpcRequest } from 'store/rpc/types' -import * as Sentry from '@sentry/react-native' -import Logger from 'utils/Logger' -import { isBtcAddress } from 'utils/isBtcAddress' -import SendServiceBTC from 'services/send/SendServiceBTC' -import { SendState } from 'services/send/types' -import { getBitcoinNetwork } from 'services/network/utils/providerUtils' -import { selectSelectedCurrency } from 'store/settings/currency' -import WalletService from 'services/wallet/WalletService' -import NetworkService from 'services/network/NetworkService' -import { - showTransactionErrorToast, - showTransactionSuccessToast -} from 'utils/toast' -import { - ApproveResponse, - DEFERRED_RESULT, - HandleResponse, - RpcRequestHandler -} from '../types' -import { parseRequestParams } from './utils' - -export type BitcoinSendTransactionApproveData = { - sendState: SendState -} - -export type BitcoinSendTransactionRpcRequest = - RpcRequest - -class BitcoinSendTransactionHandler - implements - RpcRequestHandler< - BitcoinSendTransactionRpcRequest, - never, - string, - BitcoinSendTransactionApproveData - > -{ - methods = [RpcMethod.BITCOIN_SEND_TRANSACTION] - - handle = async ( - request: BitcoinSendTransactionRpcRequest, - listenerApi: AppListenerEffectAPI - ): HandleResponse => { - const { getState } = listenerApi - const state = getState() - const isDeveloperMode = selectIsDeveloperMode(state) - const activeAccount = selectActiveAccount(state) - const parseResult = parseRequestParams(request.data.params.request.params) - - if (!parseResult.success) { - return { - success: false, - error: rpcErrors.invalidParams('Missing mandatory param(s)') - } - } - - const [address, amountSatoshi, feeRate] = parseResult.data - - // If destination address is not valid, return error - if (!isBtcAddress(address ?? '', !isDeveloperMode)) { - return { - success: false, - error: rpcErrors.invalidParams('Not a valid address.') - } - } - - if (!activeAccount) { - return { - success: false, - error: rpcErrors.invalidRequest('No active account found') - } - } - - if (!activeAccount.addressBTC) { - return { - success: false, - error: rpcErrors.invalidRequest( - 'The active account does not support BTC transactions' - ) - } - } - - const sendState: SendState = { - address, - amount: BigInt(amountSatoshi), - defaultMaxFeePerGas: BigInt(feeRate) - } - - const verifiedState = await SendServiceBTC.validateStateAndCalculateFees({ - sendState, - isMainnet: !isDeveloperMode, - fromAddress: activeAccount.addressBTC - }) - - // If we cant construct the transaction return error - if (verifiedState.error?.error) { - return { - success: false, - error: rpcErrors.transactionRejected(verifiedState.error.message) - } - } - - // If we cant submit the transaction return error - if (!verifiedState.canSubmit) { - return { - success: false, - error: rpcErrors.invalidParams('Unable to construct the transaction.') - } - } - - const approveData: BitcoinSendTransactionApproveData = { - sendState: verifiedState - } - - Navigation.navigate({ - name: AppNavigation.Root.Wallet, - params: { - screen: AppNavigation.Modal.BitcoinSendTransaction, - params: { request, data: approveData } - } - }) - - return { success: true, value: DEFERRED_RESULT } - } - - approve = async ( - payload: { - request: BitcoinSendTransactionRpcRequest - data: BitcoinSendTransactionApproveData - }, - listenerApi: AppListenerEffectAPI - ): ApproveResponse => { - try { - const { getState } = listenerApi - const { - data: { sendState } - } = payload - - // Parse the json into a tx object - const isDeveloperMode = selectIsDeveloperMode(getState()) - const currency = selectSelectedCurrency(getState()) - const activeAccount = selectActiveAccount(getState()) - const btcNetwork = getBitcoinNetwork(isDeveloperMode) - - if (!activeAccount?.addressBTC) { - throw new Error('The active account does not support BTC transactions') - } - - const txRequest = await SendServiceBTC.getTransactionRequest({ - sendState, - isMainnet: !isDeveloperMode, - fromAddress: activeAccount.addressBTC, - currency - }) - - const result = await WalletService.sign({ - transaction: txRequest, - accountIndex: activeAccount.index, - network: btcNetwork - }) - - const txHash = await NetworkService.sendTransaction({ - signedTx: result, - network: btcNetwork - }) - - showTransactionSuccessToast({ - message: 'Transaction Successful', - txHash - }) - - return { - success: true, - value: txHash - } - } catch (e) { - Logger.error( - 'Unable to approve send btc transaction request', - JSON.stringify(e) - ) - - const message = - 'message' in (e as Error) - ? (e as Error).message - : 'Send Btc transaction error' - - Sentry.captureException(e, { - tags: { dapps: 'bitcoinSendTransaction' } - }) - - showTransactionErrorToast({ message: 'Transaction Failed' }) - - return { - success: false, - error: rpcErrors.internal(message) - } - } - } -} - -export const bitcoinSendTransactionHandler = new BitcoinSendTransactionHandler() diff --git a/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/utils.ts b/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/utils.ts deleted file mode 100644 index 30cfc0bc0..000000000 --- a/packages/core-mobile/app/store/rpc/handlers/bitcoin_sendTransaction/utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SafeParseReturnType, z } from 'zod' - -const paramsSchema = z.tuple([ - z.string().describe('bitcoin receiving address'), - z.string().describe('send amount'), - z.number().describe('fee rate') -]) - -export const parseRequestParams = ( - params: unknown -): SafeParseReturnType => { - return paramsSchema.safeParse(params) -} - -export type TransactionParams = [string, string, number] diff --git a/packages/core-mobile/app/store/rpc/handlers/index.ts b/packages/core-mobile/app/store/rpc/handlers/index.ts index 3120975ac..794e92032 100644 --- a/packages/core-mobile/app/store/rpc/handlers/index.ts +++ b/packages/core-mobile/app/store/rpc/handlers/index.ts @@ -14,7 +14,6 @@ import { avalancheGetAccountPubKeyHandler } from './avalanche_getAccountPubKey/a import { avalancheSetDeveloperModeHandler } from './avalanche_setDeveloperMode/avalanche_setDeveloperMode' import { walletGetEthereumChainHandler } from './chain/wallet_getEthereumChain/wallet_getEthereumChain' import { avalancheGetAddressesInRangeHandler } from './avalanche_getAddressesInRange/avalanche_getAddressesInRange' -import { bitcoinSendTransactionHandler } from './bitcoin_sendTransaction/bitcoin_sendTransaction' const handlerMap = [ avalancheSelectAccountHandler, @@ -31,8 +30,7 @@ const handlerMap = [ avalancheSendTransactionHandler, avalancheGetAccountPubKeyHandler, avalancheSetDeveloperModeHandler, - avalancheGetAddressesInRangeHandler, - bitcoinSendTransactionHandler + avalancheGetAddressesInRangeHandler ].reduce((acc, current) => { if (current?.methods === undefined) return acc diff --git a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/namespaces.ts b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/namespaces.ts index bc877729c..4379cfc65 100644 --- a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/namespaces.ts +++ b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/namespaces.ts @@ -1,9 +1,21 @@ -import { AvalancheCaip2ChainId } from '@avalabs/core-chains-sdk' +import { + AvalancheCaip2ChainId, + BitcoinCaip2ChainId, + BlockchainNamespace +} from '@avalabs/core-chains-sdk' import { ProposalTypes } from '@walletconnect/types' -import { CORE_NONEVM_METHODS } from 'store/rpc/types' +import { CORE_AVAX_METHODS, CORE_BTC_METHODS } from 'store/rpc/types' -export const NONEVM_OPTIONAL_NAMESPACES: ProposalTypes.OptionalNamespaces = { - avax: { +export const COMMON_EVENTS = [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' +] + +export const NON_EVM_OPTIONAL_NAMESPACES: ProposalTypes.OptionalNamespaces = { + [BlockchainNamespace.AVAX]: { chains: [ AvalancheCaip2ChainId.C, AvalancheCaip2ChainId.C_TESTNET, @@ -12,13 +24,12 @@ export const NONEVM_OPTIONAL_NAMESPACES: ProposalTypes.OptionalNamespaces = { AvalancheCaip2ChainId.X, AvalancheCaip2ChainId.X_TESTNET ], - methods: CORE_NONEVM_METHODS, - events: [ - 'chainChanged', - 'accountsChanged', - 'message', - 'disconnect', - 'connect' - ] + methods: CORE_AVAX_METHODS, + events: COMMON_EVENTS + }, + [BlockchainNamespace.BIP122]: { + chains: [BitcoinCaip2ChainId.MAINNET, BitcoinCaip2ChainId.TESTNET], + methods: CORE_BTC_METHODS, + events: COMMON_EVENTS } } diff --git a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/utils.ts b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/utils.ts index 8a66d1015..77364fc1e 100644 --- a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/utils.ts +++ b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/utils.ts @@ -3,7 +3,8 @@ import AppNavigation from 'navigation/AppNavigation' import { Networks } from 'store/network/types' import { CORE_EVM_METHODS, - CORE_NONEVM_METHODS, + CORE_AVAX_METHODS, + CORE_BTC_METHODS, RpcMethod } from 'store/rpc/types' import { SafeParseError, SafeParseSuccess, z, ZodArray } from 'zod' @@ -18,7 +19,7 @@ import { onRequestRejected } from 'store/rpc/slice' import { AnyAction, Dispatch } from '@reduxjs/toolkit' import { AlertType } from '@avalabs/vm-module-types' import { ProposalTypes } from '@walletconnect/types' -import { isAVMChainId, isPVMChainId } from 'temp/caip2ChainIds' +import { isAVMChainId, isBtcChainId, isPVMChainId } from 'temp/caip2ChainIds' const CORE_WEB_HOSTNAMES = [ 'localhost', @@ -46,7 +47,9 @@ const CORE_WEB_URLS_REGEX = [ ] export const isCoreMethod = (method: string): boolean => - [...CORE_EVM_METHODS, ...CORE_NONEVM_METHODS].includes(method as RpcMethod) + [...CORE_EVM_METHODS, ...CORE_AVAX_METHODS, ...CORE_BTC_METHODS].includes( + method as RpcMethod + ) export const isCoreDomain = (url: string): boolean => { let hostname = '' @@ -80,7 +83,11 @@ export const isNetworkSupported = ( return [NetworkVMType.EVM].includes(network.vmName) } - return isAVMChainId(caip2ChainId) || isPVMChainId(caip2ChainId) + return ( + isAVMChainId(caip2ChainId) || + isPVMChainId(caip2ChainId) || + isBtcChainId(caip2ChainId) + ) } export type CoreAccountAddresses = z.infer @@ -93,6 +100,8 @@ export const getAddressForChainId = ( ? account.addressAVM : isPVMChainId(caip2ChainId) ? account.addressPVM + : isBtcChainId(caip2ChainId) + ? account.addressBTC : account.addressC } diff --git a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.test.ts b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.test.ts index 40911a03e..dfc0fb5ef 100644 --- a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.test.ts +++ b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.test.ts @@ -57,7 +57,13 @@ const validRequiredNamespaces = { eip155: { methods: ['eth_sendTransaction', 'personal_sign'], chains: ['eip155:43114', 'eip155:1'], - events: ['chainChanged', 'accountsChanged'], + events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ], rpcMap: { '1': 'https://rpc.ankr.com/eth', '43114': 'https://api.avax.network/ext/bc/C/rpc' @@ -68,7 +74,13 @@ const validRequiredNamespaces = { const testNamespacesToApprove = { eip155: { chains: ['eip155:43114', 'eip155:1'], - events: ['chainChanged', 'accountsChanged'], + events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ], methods: ['eth_sendTransaction', 'personal_sign'] } } @@ -84,7 +96,6 @@ const testNonEVMNamespacesToApprove = { methods: [ RpcMethod.AVALANCHE_SEND_TRANSACTION, RpcMethod.AVALANCHE_SIGN_TRANSACTION, - RpcMethod.BITCOIN_SEND_TRANSACTION, RpcMethod.AVALANCHE_SIGN_MESSAGE ], events: [ @@ -94,6 +105,20 @@ const testNonEVMNamespacesToApprove = { 'disconnect', 'connect' ] + }, + bip122: { + chains: [ + 'bip122:000000000019d6689c085ae165831e93', + 'bip122:000000000933ea01ad0ee984209779ba' + ], + methods: [RpcMethod.BITCOIN_SEND_TRANSACTION], + events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ] } } @@ -173,8 +198,7 @@ const testApproveInvalidData = async (data: unknown) => { }) } -// eslint-disable-next-line jest/no-disabled-tests -xdescribe('session_request handler', () => { +describe('session_request handler', () => { it('should contain correct methods', () => { expect(handler.methods).toEqual(['wc_sessionRequest']) }) @@ -272,14 +296,20 @@ xdescribe('session_request handler', () => { eip155: { methods: ['eth_sendTransaction'], chains: ['eip155:43114', 'eip155:1'], - events: ['chainChanged', 'accountsChanged'] + events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ] } } const testRequest = createRequest(testRequiredNamespaces) jest .spyOn(require('store/network/slice'), 'selectActiveNetwork') - .mockReturnValueOnce(mockNetworks[4503599627370475]) + .mockReturnValueOnce(mockNetworks[432204]) const mockRequest = jest.fn().mockResolvedValue({}) const { @@ -291,6 +321,7 @@ xdescribe('session_request handler', () => { await handler.handle(testRequest, mockListenerApi) expect(mockRequest).toHaveBeenCalledWith({ + chainId: 'eip155:43114', method: RpcMethod.WALLET_SWITCH_ETHEREUM_CHAIN, params: [ { @@ -557,12 +588,12 @@ xdescribe('session_request handler', () => { accounts: [ 'avax:Rr9hnPVPxuUvrdCul-vjEsU1zmqKqRDo:pvmAddress1', 'avax:Rr9hnPVPxuUvrdCul-vjEsU1zmqKqRDo:pvmAddress2', - 'avax:Sj7NVE3jXTbJvwFAiu7OEUo_8g8ctXMG:avmAddress1', - 'avax:Sj7NVE3jXTbJvwFAiu7OEUo_8g8ctXMG:avmAddress2', + 'avax:Sj7NVE3jXTbJvwFAiu7OEUo_8g8ctXMG:pvmAddress1', + 'avax:Sj7NVE3jXTbJvwFAiu7OEUo_8g8ctXMG:pvmAddress2', 'avax:imji8papUf2EhV3le337w1vgFauqkJg-:avmAddress1', 'avax:imji8papUf2EhV3le337w1vgFauqkJg-:avmAddress2', - 'avax:8AJTpRj3SAqv1e80Mtl9em08LhvKEbkl:pvmAddress1', - 'avax:8AJTpRj3SAqv1e80Mtl9em08LhvKEbkl:pvmAddress2' + 'avax:8AJTpRj3SAqv1e80Mtl9em08LhvKEbkl:avmAddress1', + 'avax:8AJTpRj3SAqv1e80Mtl9em08LhvKEbkl:avmAddress2' ], chains: [ 'avax:Rr9hnPVPxuUvrdCul-vjEsU1zmqKqRDo', @@ -570,13 +601,38 @@ xdescribe('session_request handler', () => { 'avax:imji8papUf2EhV3le337w1vgFauqkJg-', 'avax:8AJTpRj3SAqv1e80Mtl9em08LhvKEbkl' ], - events: ['chainChanged', 'accountsChanged'], + events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ], methods: [ 'avalanche_sendTransaction', 'avalanche_signTransaction', - 'bitcoin_sendTransaction', 'avalanche_signMessage' ] + }, + bip122: { + accounts: [ + 'bip122:000000000019d6689c085ae165831e93:btcAddress1', + 'bip122:000000000019d6689c085ae165831e93:btcAddress2', + 'bip122:000000000933ea01ad0ee984209779ba:btcAddress1', + 'bip122:000000000933ea01ad0ee984209779ba:btcAddress2' + ], + chains: [ + 'bip122:000000000019d6689c085ae165831e93', + 'bip122:000000000933ea01ad0ee984209779ba' + ], + events: [ + 'chainChanged', + 'accountsChanged', + 'message', + 'disconnect', + 'connect' + ], + methods: ['bitcoin_sendTransaction'] } } diff --git a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.ts b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.ts index f94069afc..b91744d6a 100644 --- a/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.ts +++ b/packages/core-mobile/app/store/rpc/handlers/wc_sessionRequest/wc_sessionRequest.ts @@ -2,19 +2,20 @@ import { ProposalTypes, SessionTypes } from '@walletconnect/types' import { AppListenerEffectAPI } from 'store' import { rpcErrors } from '@metamask/rpc-errors' import { normalizeNamespaces } from '@walletconnect/utils' +import { BlockchainNamespace } from '@avalabs/core-chains-sdk' import { WCSessionProposal } from 'store/walletConnectV2/types' import { selectActiveNetwork, selectAllNetworks, selectFavoriteNetworks -} from 'store/network' +} from 'store/network/slice' import { selectIsBlockaidDappScanBlocked } from 'store/posthog/slice' import { createInAppRequest } from 'store/rpc/utils/createInAppRequest' import { getChainIdFromCaip2 } from 'temp/caip2ChainIds' import mergeWith from 'lodash/mergeWith' import isArray from 'lodash/isArray' import union from 'lodash/union' -import { RpcMethod, CORE_EVM_METHODS, BlockchainNamespace } from '../../types' +import { RpcMethod, CORE_EVM_METHODS } from '../../types' import { RpcRequestHandler, DEFERRED_RESULT, @@ -32,9 +33,7 @@ import { parseApproveData, scanAndSessionProposal } from './utils' -import { NONEVM_OPTIONAL_NAMESPACES } from './namespaces' - -const DEFAULT_EVENTS = ['chainChanged', 'accountsChanged'] +import { NON_EVM_OPTIONAL_NAMESPACES, COMMON_EVENTS } from './namespaces' const supportedMethods = [ RpcMethod.ETH_SEND_TRANSACTION, @@ -52,7 +51,7 @@ const supportedMethods = [ class WCSessionRequestHandler implements RpcRequestHandler { methods = [RpcMethod.WC_SESSION_REQUEST] - private getApprovedMethods = (dappUrl: string): RpcMethod[] => { + private getApprovedEvmMethods = (dappUrl: string): RpcMethod[] => { const isCoreApp = isCoreDomain(dappUrl) // approve all methods that we support here to allow dApps @@ -64,12 +63,13 @@ class WCSessionRequestHandler implements RpcRequestHandler { } private getApprovedEvents = ( - requiredNamespaces: ProposalTypes.RequiredNamespaces + requiredNamespaces: ProposalTypes.RequiredNamespaces, + namespace: string ): string[] => { return [ ...new Set([ - ...DEFAULT_EVENTS, - ...(requiredNamespaces[BlockchainNamespace.EIP155]?.events ?? []) + ...COMMON_EVENTS, + ...(requiredNamespaces[namespace]?.events ?? []) ]) ] } @@ -127,7 +127,6 @@ class WCSessionRequestHandler implements RpcRequestHandler { try { const chainId = chainIdtoSwitch.toString() - const request = createInAppRequest(listenerApi.dispatch) await request({ method: RpcMethod.WALLET_SWITCH_ETHEREUM_CHAIN, @@ -211,7 +210,7 @@ class WCSessionRequestHandler implements RpcRequestHandler { // it throws an error when we add these non-EVM namespaces in the dapp. // This is a temporary fix until core web supports these namespaces isCoreApp - ? { ...optionalNamespaces, ...NONEVM_OPTIONAL_NAMESPACES } + ? { ...optionalNamespaces, ...NON_EVM_OPTIONAL_NAMESPACES } : optionalNamespaces ) @@ -281,8 +280,6 @@ class WCSessionRequestHandler implements RpcRequestHandler { const dappUrl = payload.request.data.params.proposer.metadata.url - const events = this.getApprovedEvents(requiredNamespaces) - const namespacesToApprove = result.data.namespaces const selectedAccounts = result.data.selectedAccounts @@ -298,9 +295,11 @@ class WCSessionRequestHandler implements RpcRequestHandler { const methods = namespace === BlockchainNamespace.EIP155 - ? this.getApprovedMethods(dappUrl) + ? this.getApprovedEvmMethods(dappUrl) : namespaceToApprove.methods + const events = this.getApprovedEvents(requiredNamespaces, namespace) + namespaces[namespace] = { chains: namespaceToApprove.chains, accounts, diff --git a/packages/core-mobile/app/store/rpc/listeners/index.test.ts b/packages/core-mobile/app/store/rpc/listeners/index.test.ts index 7938afdaa..bd78a977e 100644 --- a/packages/core-mobile/app/store/rpc/listeners/index.test.ts +++ b/packages/core-mobile/app/store/rpc/listeners/index.test.ts @@ -41,6 +41,7 @@ jest.mock('store/account/slice', () => { const mockOnRpcRequest = jest.fn() const mockModule: Module = { + getProvider: jest.fn(), getManifest: jest.fn(), getBalances: jest.fn(), getTransactionHistory: jest.fn(), diff --git a/packages/core-mobile/app/store/rpc/providers/walletConnect/walletConnect.ts b/packages/core-mobile/app/store/rpc/providers/walletConnect/walletConnect.ts index fa661a1e1..4969e2128 100644 --- a/packages/core-mobile/app/store/rpc/providers/walletConnect/walletConnect.ts +++ b/packages/core-mobile/app/store/rpc/providers/walletConnect/walletConnect.ts @@ -9,8 +9,8 @@ import { selectActiveAccount } from 'store/account' import { selectActiveNetwork } from 'store/network' import { UPDATE_SESSION_DELAY } from 'consts/walletConnect' import AnalyticsService from 'services/analytics/AnalyticsService' -import { getChainIdFromRequest } from 'store/rpc/utils/getChainIdFromRequest/getChainIdFromRequest' import { showDappConnectionSuccessToast } from 'utils/toast' +import { getChainIdFromCaip2 } from 'temp/caip2ChainIds' import { AgnosticRpcProvider, RpcMethod, RpcProvider } from '../../types' import { isSessionProposal, isUserRejectedError } from './utils' @@ -152,9 +152,20 @@ class WalletConnectProvider implements AgnosticRpcProvider { const isDeveloperMode = selectIsDeveloperMode(state) // validate chain against the current developer mode - const chainId = getChainIdFromRequest(request) + const caip2ChainId = request.data.params.chainId + const chainId = getChainIdFromCaip2(caip2ChainId) + + if (chainId === undefined) { + throw rpcErrors.internal('Invalid chainId') + } + const network = selectNetwork(chainId)(state) - const isTestnet = Boolean(network?.isTestnet) + + if (network === undefined) { + throw rpcErrors.internal('Invalid chainId') + } + + const isTestnet = Boolean(network.isTestnet) if (isTestnet !== isDeveloperMode) { const message = isDeveloperMode diff --git a/packages/core-mobile/app/store/rpc/types.ts b/packages/core-mobile/app/store/rpc/types.ts index 926e01140..7a4b9a196 100644 --- a/packages/core-mobile/app/store/rpc/types.ts +++ b/packages/core-mobile/app/store/rpc/types.ts @@ -81,13 +81,14 @@ export const CORE_EVM_METHODS = [ RpcMethod.AVALANCHE_GET_ADDRESSES_IN_RANGE ] -export const CORE_NONEVM_METHODS = [ +export const CORE_AVAX_METHODS = [ RpcMethod.AVALANCHE_SEND_TRANSACTION, RpcMethod.AVALANCHE_SIGN_TRANSACTION, - RpcMethod.BITCOIN_SEND_TRANSACTION, RpcMethod.AVALANCHE_SIGN_MESSAGE ] +export const CORE_BTC_METHODS = [RpcMethod.BITCOIN_SEND_TRANSACTION] + export type ConfirmationReceiptStatus = 'Reverted' | 'Success' | 'Pending' export type RequestStatus = { @@ -136,11 +137,3 @@ export const CORE_MOBILE_META: PeerMeta = { url: 'https://core.app/', icons: [] } - -export enum BlockchainNamespace { - EIP155 = 'eip155', // Evm - AVAX = 'avax', // Avalanche - BIP122 = 'bip122' // Bitcoin -} - -export const AVM_PVM_IDENTIFIER = 'avax' diff --git a/packages/core-mobile/app/store/rpc/utils/getChainIdFromRequest/getChainIdFromRequest.ts b/packages/core-mobile/app/store/rpc/utils/getChainIdFromRequest/getChainIdFromRequest.ts index f3e170cfa..66a44dae9 100644 --- a/packages/core-mobile/app/store/rpc/utils/getChainIdFromRequest/getChainIdFromRequest.ts +++ b/packages/core-mobile/app/store/rpc/utils/getChainIdFromRequest/getChainIdFromRequest.ts @@ -1,3 +1,4 @@ +import { rpcErrors } from '@metamask/rpc-errors' import { RpcMethod, RpcRequest } from '../../types' export const getChainIdFromRequest = ( @@ -9,7 +10,7 @@ export const getChainIdFromRequest = ( const parts = request.data.params.chainId.split(':') if (parts.length < 2 || isNaN(Number(parts[1]))) { - throw new Error('chainId is not in a valid format') + throw rpcErrors.internal('chainId is not in a valid format') } return Number(parts[1]) diff --git a/packages/core-mobile/app/temp/caip2ChainIds.ts b/packages/core-mobile/app/temp/caip2ChainIds.ts index 6fde83e9e..d3713b5d7 100644 --- a/packages/core-mobile/app/temp/caip2ChainIds.ts +++ b/packages/core-mobile/app/temp/caip2ChainIds.ts @@ -1,6 +1,10 @@ import { JsonMap } from 'store/posthog' -import { ChainId } from '@avalabs/core-chains-sdk' -import { BlockchainNamespace } from 'store/rpc/types' +import { + AvalancheCaip2ChainId, + BitcoinCaip2ChainId, + BlockchainNamespace, + ChainId +} from '@avalabs/core-chains-sdk' /** * In the process of switching to CAIP2 naming convention for blockchain ids we are temporarily modifying Posthog @@ -41,30 +45,24 @@ enum BlockchainId { X_CHAIN_TESTNET = `${BlockchainNamespace.AVAX}:2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm` } -// Based on CAIP-2, hashed -enum AvalancheChainId { - P_CHAIN = `${BlockchainNamespace.AVAX}:Rr9hnPVPxuUvrdCul-vjEsU1zmqKqRDo`, - P_CHAIN_TESTNET = `${BlockchainNamespace.AVAX}:8AJTpRj3SAqv1e80Mtl9em08LhvKEbkl`, - X_CHAIN = `${BlockchainNamespace.AVAX}:imji8papUf2EhV3le337w1vgFauqkJg-`, - X_CHAIN_TESTNET = `${BlockchainNamespace.AVAX}:Sj7NVE3jXTbJvwFAiu7OEUo_8g8ctXMG` -} - -enum BitcoinChainId { - MAINNET = `${BlockchainNamespace.BIP122}:000000000019d6689c085ae165831e93`, - TESTNET = `${BlockchainNamespace.BIP122}:000000000933ea01ad0ee984209779ba` -} - export const isPVMChainId = (caip2ChainId: string): boolean => { return ( - caip2ChainId === AvalancheChainId.P_CHAIN || - caip2ChainId === AvalancheChainId.P_CHAIN_TESTNET + caip2ChainId === AvalancheCaip2ChainId.P || + caip2ChainId === AvalancheCaip2ChainId.P_TESTNET ) } export const isAVMChainId = (caip2ChainId: string): boolean => { return ( - caip2ChainId === AvalancheChainId.X_CHAIN || - caip2ChainId === AvalancheChainId.X_CHAIN_TESTNET + caip2ChainId === AvalancheCaip2ChainId.X || + caip2ChainId === AvalancheCaip2ChainId.X_TESTNET + ) +} + +export const isBtcChainId = (caip2ChainId: string): boolean => { + return ( + caip2ChainId === BitcoinCaip2ChainId.MAINNET || + caip2ChainId === BitcoinCaip2ChainId.TESTNET ) } @@ -72,13 +70,13 @@ export const getAvalancheCaip2ChainId = ( chainId: number ): string | undefined => { if (chainId === ChainId.AVALANCHE_P) { - return AvalancheChainId.P_CHAIN + return AvalancheCaip2ChainId.P } else if (chainId === ChainId.AVALANCHE_TEST_P) { - return AvalancheChainId.P_CHAIN_TESTNET + return AvalancheCaip2ChainId.P_TESTNET } else if (chainId === ChainId.AVALANCHE_X) { - return AvalancheChainId.X_CHAIN + return AvalancheCaip2ChainId.X } else if (chainId === ChainId.AVALANCHE_TEST_X) { - return AvalancheChainId.X_CHAIN_TESTNET + return AvalancheCaip2ChainId.X_TESTNET } return undefined } @@ -86,13 +84,13 @@ export const getAvalancheCaip2ChainId = ( export const getAvalancheChainId = ( caip2ChainId: string ): number | undefined => { - if (caip2ChainId === AvalancheChainId.P_CHAIN) { + if (caip2ChainId === AvalancheCaip2ChainId.P) { return ChainId.AVALANCHE_P - } else if (caip2ChainId === AvalancheChainId.P_CHAIN_TESTNET) { + } else if (caip2ChainId === AvalancheCaip2ChainId.P_TESTNET) { return ChainId.AVALANCHE_TEST_P - } else if (caip2ChainId === AvalancheChainId.X_CHAIN) { + } else if (caip2ChainId === AvalancheCaip2ChainId.X) { return ChainId.AVALANCHE_X - } else if (caip2ChainId === AvalancheChainId.X_CHAIN_TESTNET) { + } else if (caip2ChainId === AvalancheCaip2ChainId.X_TESTNET) { return ChainId.AVALANCHE_TEST_X } @@ -100,9 +98,9 @@ export const getAvalancheChainId = ( } export const getBitcoinChainId = (caip2ChainId: string): number | undefined => { - if (caip2ChainId === BitcoinChainId.MAINNET) { + if (caip2ChainId === BitcoinCaip2ChainId.MAINNET) { return ChainId.BITCOIN - } else if (caip2ChainId === BitcoinChainId.TESTNET) { + } else if (caip2ChainId === BitcoinCaip2ChainId.TESTNET) { return ChainId.BITCOIN_TESTNET } @@ -110,7 +108,18 @@ export const getBitcoinChainId = (caip2ChainId: string): number | undefined => { } export const getBitcoinCaip2ChainId = (isMainnet: boolean): string => { - return isMainnet ? BitcoinChainId.MAINNET : BitcoinChainId.TESTNET + return isMainnet ? BitcoinCaip2ChainId.MAINNET : BitcoinCaip2ChainId.TESTNET +} + +export const getBitcoinCaip2ChainIdByChainId = ( + chainId: number +): string | undefined => { + if (chainId === ChainId.BITCOIN) { + return BitcoinCaip2ChainId.MAINNET + } else if (chainId === ChainId.BITCOIN_TESTNET) { + return BitcoinCaip2ChainId.TESTNET + } + return undefined } export const getEvmCaip2ChainId = (chainId: number): string => { diff --git a/packages/core-mobile/app/utils/data/caip.ts b/packages/core-mobile/app/utils/data/caip.ts index b2ec1d870..bda8f7a86 100644 --- a/packages/core-mobile/app/utils/data/caip.ts +++ b/packages/core-mobile/app/utils/data/caip.ts @@ -1,5 +1,5 @@ import { caip2 } from '@avalabs/bridge-unified' -import { BlockchainNamespace } from 'store/rpc' +import { BlockchainNamespace } from '@avalabs/core-chains-sdk' export const chainIdToCaip = ( chainId: number, diff --git a/packages/core-mobile/app/utils/getJsonRpcErrorMessage.ts b/packages/core-mobile/app/utils/getJsonRpcErrorMessage.ts new file mode 100644 index 000000000..84f3b0a72 --- /dev/null +++ b/packages/core-mobile/app/utils/getJsonRpcErrorMessage.ts @@ -0,0 +1,13 @@ +import { JsonRpcError } from '@metamask/rpc-errors' + +export const getJsonRpcErrorMessage = (error: unknown): string => { + if (error instanceof JsonRpcError) { + return `${error.message}${error.data.cause ? '.\n' + error.data.cause : ''}` + } + + if (error instanceof Error) { + return error.message + } + + return 'Unexpected error' +} diff --git a/packages/core-mobile/app/vmModule/ApprovalController/ApprovalController.ts b/packages/core-mobile/app/vmModule/ApprovalController/ApprovalController.ts index ace8ee476..4542b9854 100644 --- a/packages/core-mobile/app/vmModule/ApprovalController/ApprovalController.ts +++ b/packages/core-mobile/app/vmModule/ApprovalController/ApprovalController.ts @@ -15,9 +15,10 @@ import { showTransactionErrorToast, showTransactionSuccessToast } from 'utils/toast' -import { handleEthSendTransaction } from './handleEthSendTransaction' -import { handleSignMessage } from './handleSignMessage' -import { handleAvalancheSignTransaction } from './handleAvalancheSignTransaction' +import { avalancheSignTransaction } from '../handlers/avalancheSignTransaction' +import { ethSendTransaction } from '../handlers/ethSendTransaction' +import { signMessage } from '../handlers/signMessage' +import { btcSendTransaction } from '../handlers/btcSendTransaction' class ApprovalController implements VmModuleApprovalController { onTransactionConfirmed(txHash: Hex): void { @@ -51,8 +52,19 @@ class ApprovalController implements VmModuleApprovalController { overrideData?: string }): Promise => { switch (signingData.type) { + case RpcMethod.BITCOIN_SEND_TRANSACTION: { + btcSendTransaction({ + transactionData: signingData.data, + finalFeeRate: Number(maxFeePerGas || 0), + account, + network, + resolve + }) + + break + } case RpcMethod.ETH_SEND_TRANSACTION: { - handleEthSendTransaction({ + ethSendTransaction({ transactionRequest: signingData.data, network, account, @@ -71,7 +83,7 @@ class ApprovalController implements VmModuleApprovalController { case RpcMethod.SIGN_TYPED_DATA_V3: case RpcMethod.SIGN_TYPED_DATA_V4: case RpcMethod.AVALANCHE_SIGN_MESSAGE: { - handleSignMessage({ + signMessage({ method: signingData.type, data: signingData.data, account, @@ -82,7 +94,7 @@ class ApprovalController implements VmModuleApprovalController { break } case RpcMethod.AVALANCHE_SIGN_TRANSACTION: { - handleAvalancheSignTransaction({ + avalancheSignTransaction({ unsignedTxJson: signingData.unsignedTxJson, ownSignatureIndices: signingData.ownSignatureIndices, account, diff --git a/packages/core-mobile/app/vmModule/ModuleManager.ts b/packages/core-mobile/app/vmModule/ModuleManager.ts index 431ec5650..e93aa8bf7 100644 --- a/packages/core-mobile/app/vmModule/ModuleManager.ts +++ b/packages/core-mobile/app/vmModule/ModuleManager.ts @@ -1,12 +1,15 @@ import { EvmModule } from '@avalabs/evm-module' import Logger from 'utils/Logger' import { Environment, GetAddressParams, Module } from '@avalabs/vm-module-types' -import { NetworkVMType, Network } from '@avalabs/core-chains-sdk' +import { + NetworkVMType, + Network, + BlockchainNamespace +} from '@avalabs/core-chains-sdk' import { assertNotUndefined } from 'utils/assertions' import { AvalancheModule } from '@avalabs/avalanche-module' import { BlockchainId } from '@avalabs/glacier-sdk' import { BitcoinModule } from '@avalabs/bitcoin-module' -import { BlockchainNamespace } from 'store/rpc/types' import { getBitcoinCaip2ChainId, getEvmCaip2ChainId } from 'temp/caip2ChainIds' import { ModuleErrors, VmModuleErrors } from './errors' import { approvalController } from './ApprovalController/ApprovalController' diff --git a/packages/core-mobile/app/vmModule/ApprovalController/handleAvalancheSignTransaction.ts b/packages/core-mobile/app/vmModule/handlers/avalancheSignTransaction.ts similarity index 98% rename from packages/core-mobile/app/vmModule/ApprovalController/handleAvalancheSignTransaction.ts rename to packages/core-mobile/app/vmModule/handlers/avalancheSignTransaction.ts index 42cfd3fda..9c5d99522 100644 --- a/packages/core-mobile/app/vmModule/ApprovalController/handleAvalancheSignTransaction.ts +++ b/packages/core-mobile/app/vmModule/handlers/avalancheSignTransaction.ts @@ -7,7 +7,7 @@ import { Avalanche } from '@avalabs/core-wallets-sdk' import Logger from 'utils/Logger' import { Network } from '@avalabs/core-chains-sdk' -export const handleAvalancheSignTransaction = async ({ +export const avalancheSignTransaction = async ({ unsignedTxJson, account, ownSignatureIndices, diff --git a/packages/core-mobile/app/vmModule/handlers/btcSendTransaction.ts b/packages/core-mobile/app/vmModule/handlers/btcSendTransaction.ts new file mode 100644 index 000000000..fb7d2f243 --- /dev/null +++ b/packages/core-mobile/app/vmModule/handlers/btcSendTransaction.ts @@ -0,0 +1,67 @@ +import { Network } from '@avalabs/core-chains-sdk' +import { + ApprovalResponse, + BitcoinTransactionData +} from '@avalabs/vm-module-types' +import WalletService from 'services/wallet/WalletService' +import { rpcErrors } from '@metamask/rpc-errors' +import { Account } from 'store/account/types' +import { BtcTransactionRequest } from 'services/wallet/types' +import { BitcoinInputUTXO, createTransferTx } from '@avalabs/core-wallets-sdk' +import ModuleManager from 'vmModule/ModuleManager' + +export const btcSendTransaction = async ({ + transactionData, + network, + account, + finalFeeRate, + resolve +}: { + transactionData: BitcoinTransactionData + network: Network + account: Account + finalFeeRate: number + resolve: (value: ApprovalResponse) => void +}): Promise => { + const { to, amount, balance, feeRate, inputs, outputs } = transactionData + + try { + let transaction: BtcTransactionRequest = { inputs, outputs } + + // we need to re-create the transaction when fee rate has changed + if (finalFeeRate !== 0 && finalFeeRate !== feeRate) { + const provider = ModuleManager.bitcoinModule.getProvider(network) + const updatedTx = createTransferTx( + to, + account.addressBTC, + amount, + finalFeeRate, + balance.utxos as BitcoinInputUTXO[], + provider.getNetwork() + ) + + if (!updatedTx.inputs || !updatedTx.outputs) { + throw new Error('Unable to create transaction') + } + + transaction = { inputs: updatedTx.inputs, outputs: updatedTx.outputs } + } + + const signedTx = await WalletService.sign({ + transaction, + accountIndex: account.index, + network + }) + + resolve({ + signedData: signedTx + }) + } catch (error) { + resolve({ + error: rpcErrors.internal({ + message: 'Failed to sign btc transaction', + data: { cause: error } + }) + }) + } +} diff --git a/packages/core-mobile/app/vmModule/ApprovalController/handleEthSendTransaction.ts b/packages/core-mobile/app/vmModule/handlers/ethSendTransaction.ts similarity index 81% rename from packages/core-mobile/app/vmModule/ApprovalController/handleEthSendTransaction.ts rename to packages/core-mobile/app/vmModule/handlers/ethSendTransaction.ts index 100b75ccc..d9dafc61d 100644 --- a/packages/core-mobile/app/vmModule/ApprovalController/handleEthSendTransaction.ts +++ b/packages/core-mobile/app/vmModule/handlers/ethSendTransaction.ts @@ -1,11 +1,11 @@ import { Network } from '@avalabs/core-chains-sdk' -import { Hex, ApprovalResponse } from '@avalabs/vm-module-types' +import { ApprovalResponse } from '@avalabs/vm-module-types' import WalletService from 'services/wallet/WalletService' import { rpcErrors } from '@metamask/rpc-errors' import { Account } from 'store/account/types' import { TransactionRequest } from 'ethers' -export const handleEthSendTransaction = async ({ +export const ethSendTransaction = async ({ transactionRequest, network, account, @@ -45,11 +45,14 @@ export const handleEthSendTransaction = async ({ }) resolve({ - signedData: signedTx as Hex + signedData: signedTx }) } catch (error) { resolve({ - error: rpcErrors.internal('failed to sign evm transaction') + error: rpcErrors.internal({ + message: 'Failed to sign evm transaction', + data: { cause: error } + }) }) } } diff --git a/packages/core-mobile/app/vmModule/ApprovalController/handleSignMessage.ts b/packages/core-mobile/app/vmModule/handlers/signMessage.ts similarity index 79% rename from packages/core-mobile/app/vmModule/ApprovalController/handleSignMessage.ts rename to packages/core-mobile/app/vmModule/handlers/signMessage.ts index 8a7d09a6b..6bcc5ad0b 100644 --- a/packages/core-mobile/app/vmModule/ApprovalController/handleSignMessage.ts +++ b/packages/core-mobile/app/vmModule/handlers/signMessage.ts @@ -1,6 +1,5 @@ import { Network } from '@avalabs/core-chains-sdk' import { - Hex, ApprovalResponse, RpcMethod, TypedData, @@ -11,7 +10,7 @@ import WalletService from 'services/wallet/WalletService' import { rpcErrors } from '@metamask/rpc-errors' import { Account } from 'store/account/types' -export const handleSignMessage = async ({ +export const signMessage = async ({ method, data, account, @@ -33,11 +32,14 @@ export const handleSignMessage = async ({ }) resolve({ - signedData: signedMessage as Hex + signedData: signedMessage }) } catch (error) { resolve({ - error: rpcErrors.internal(`failed to sign ${network.vmName} message`) + error: rpcErrors.internal({ + message: `Failed to sign ${network.vmName} message`, + data: { cause: error } + }) }) } } diff --git a/packages/core-mobile/package.json b/packages/core-mobile/package.json index ebc343964..08b0c78fb 100644 --- a/packages/core-mobile/package.json +++ b/packages/core-mobile/package.json @@ -21,20 +21,20 @@ "gen:glacierApi": "npx openapi-zod-client 'https://glacier-api-dev.avax.network/api-json' -o './app/utils/network/glacierApi.client.ts'" }, "dependencies": { - "@avalabs/avalanche-module": "0.1.10", + "@avalabs/avalanche-module": "0.1.11", "@avalabs/avalanchejs": "4.0.5", - "@avalabs/bitcoin-module": "0.1.10", + "@avalabs/bitcoin-module": "0.1.11", "@avalabs/bridge-unified": "2.1.0", - "@avalabs/core-bridge-sdk": "3.1.0-alpha.0", - "@avalabs/core-chains-sdk": "3.1.0-alpha.0", - "@avalabs/core-coingecko-sdk": "3.1.0-alpha.0", - "@avalabs/core-utils-sdk": "3.1.0-alpha.0", - "@avalabs/core-wallets-sdk": "3.1.0-alpha.0", - "@avalabs/evm-module": "0.1.10", - "@avalabs/glacier-sdk": "3.1.0-alpha.0", + "@avalabs/core-bridge-sdk": "3.1.0-alpha.2", + "@avalabs/core-chains-sdk": "3.1.0-alpha.2", + "@avalabs/core-coingecko-sdk": "3.1.0-alpha.2", + "@avalabs/core-utils-sdk": "3.1.0-alpha.2", + "@avalabs/core-wallets-sdk": "3.1.0-alpha.2", + "@avalabs/evm-module": "0.1.11", + "@avalabs/glacier-sdk": "3.1.0-alpha.2", "@avalabs/k2-mobile": "workspace:*", - "@avalabs/types": "3.1.0-alpha.0", - "@avalabs/vm-module-types": "0.1.10", + "@avalabs/types": "3.1.0-alpha.2", + "@avalabs/vm-module-types": "0.1.11", "@blockaid/client": "0.10.0", "@coinbase/cbpay-js": "2.2.1", "@cubist-labs/cubesigner-sdk": "0.3.28", diff --git a/yarn.lock b/yarn.lock index 73b63fd41..f062d924a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,9 +29,9 @@ __metadata: languageName: node linkType: hard -"@avalabs/avalanche-module@npm:0.1.10": - version: 0.1.10 - resolution: "@avalabs/avalanche-module@npm:0.1.10" +"@avalabs/avalanche-module@npm:0.1.11": + version: 0.1.11 + resolution: "@avalabs/avalanche-module@npm:0.1.11" dependencies: "@avalabs/avalanchejs": 4.0.5 "@avalabs/core-chains-sdk": 3.1.0-alpha.1 @@ -41,13 +41,13 @@ __metadata: "@avalabs/core-wallets-sdk": 3.1.0-alpha.1 "@avalabs/glacier-sdk": 3.1.0-alpha.1 "@avalabs/types": 3.1.0-alpha.1 - "@avalabs/vm-module-types": 0.1.10 + "@avalabs/vm-module-types": 0.1.11 "@metamask/rpc-errors": 6.3.0 "@zodios/core": 10.9.6 big.js: 6.2.1 bn.js: 5.2.1 zod: 3.23.8 - checksum: 1eee2d8d8dbf052742d43f8c765ffc6158b4b5c6821c197da3aef6bd2804b4e55ca38da6f1fde9c0e51f9d6c3f53afcce439dfd8edcd99f06a44d4c1b1bcc2aa + checksum: ea16bed5d94a0ca7bd8307c01a3a62fd9e6082691aa0fc54d1dbbca29579d22e1943b59b3ae6b62eef5a200ca206c7af45778c3c8e211f0e9cd7c51743a75d3a languageName: node linkType: hard @@ -64,21 +64,21 @@ __metadata: languageName: node linkType: hard -"@avalabs/bitcoin-module@npm:0.1.10": - version: 0.1.10 - resolution: "@avalabs/bitcoin-module@npm:0.1.10" +"@avalabs/bitcoin-module@npm:0.1.11": + version: 0.1.11 + resolution: "@avalabs/bitcoin-module@npm:0.1.11" dependencies: "@avalabs/core-coingecko-sdk": 3.1.0-alpha.1 "@avalabs/core-utils-sdk": 3.1.0-alpha.1 "@avalabs/core-wallets-sdk": 3.1.0-alpha.1 - "@avalabs/vm-module-types": 0.1.10 + "@avalabs/vm-module-types": 0.1.11 "@metamask/rpc-errors": 6.3.0 "@zodios/core": 10.9.6 big.js: 6.2.1 bitcoinjs-lib: 5.2.0 bn.js: 5.2.1 zod: 3.23.8 - checksum: ebe1aeb02404b1831d68173437d3646271a50cc8c6e093c1681a7afaa4371b7fdbd5cf751df75eaef0f48bda31afb96ae8dc9ee4f43293cbbde87d6315c992f8 + checksum: a1f7c451820096d868f9518a75fa3546cca4d5f2f7393fe01c7ac4db3f40b10d9ac9c6dc1300bfa0e413ca1d3c6ca21a6587b7aa0e149578697ba6de880fdf90 languageName: node linkType: hard @@ -93,29 +93,20 @@ __metadata: languageName: node linkType: hard -"@avalabs/core-bridge-sdk@npm:3.1.0-alpha.0": - version: 3.1.0-alpha.0 - resolution: "@avalabs/core-bridge-sdk@npm:3.1.0-alpha.0" +"@avalabs/core-bridge-sdk@npm:3.1.0-alpha.2": + version: 3.1.0-alpha.2 + resolution: "@avalabs/core-bridge-sdk@npm:3.1.0-alpha.2" dependencies: - "@avalabs/core-coingecko-sdk": 3.1.0-alpha.0 - "@avalabs/core-utils-sdk": 3.1.0-alpha.0 - "@avalabs/core-wallets-sdk": 3.1.0-alpha.0 + "@avalabs/core-coingecko-sdk": 3.1.0-alpha.2 + "@avalabs/core-utils-sdk": 3.1.0-alpha.2 + "@avalabs/core-wallets-sdk": 3.1.0-alpha.2 peerDependencies: big.js: ^6.2.1 bitcoinjs-lib: ^5.2.0 coinselect: ^3.1.12 ethers: ^6.7.1 react: ^17.0.2 - checksum: 7f5afe6e89c46b7ca182c3f4a726cab6bfa11db1865e483db561d02001c58f85af94d239e564f27da295d995fe5ea0f3030723c363f5d7f1e64dbaf03d7104a5 - languageName: node - linkType: hard - -"@avalabs/core-chains-sdk@npm:3.1.0-alpha.0": - version: 3.1.0-alpha.0 - resolution: "@avalabs/core-chains-sdk@npm:3.1.0-alpha.0" - dependencies: - "@avalabs/core-utils-sdk": 3.1.0-alpha.0 - checksum: 099a4973b9eb1555680b29c4d095f82502d00e1cff62e3829a7b71919bfa67e61cbc38122bb469b89d7517ff88de2ad609a66d3ed589534450bb469d956b793a + checksum: 641fa2e2edd6e9769804c73eb78d94e43b090b8f9c700edb20e8f25c5bff2b217d39c00a8c41f43b9c854a9255060c04251e067adaf9cef94b60978fd4d311ea languageName: node linkType: hard @@ -128,12 +119,12 @@ __metadata: languageName: node linkType: hard -"@avalabs/core-coingecko-sdk@npm:3.1.0-alpha.0": - version: 3.1.0-alpha.0 - resolution: "@avalabs/core-coingecko-sdk@npm:3.1.0-alpha.0" +"@avalabs/core-chains-sdk@npm:3.1.0-alpha.2": + version: 3.1.0-alpha.2 + resolution: "@avalabs/core-chains-sdk@npm:3.1.0-alpha.2" dependencies: - "@avalabs/core-utils-sdk": 3.1.0-alpha.0 - checksum: ffed57bfff71dcf673e8d44ee52a8c5bdfcb6eff95bf0d3b3faa6410ce281f8d8bd4a32ca2d3f91b6756de29eadfda3fc8769350d073520717dc93b535538666 + "@avalabs/core-utils-sdk": 3.1.0-alpha.2 + checksum: 6999f850bd88f27b94b3d78bed7f2e363b4437cab656fbcbeb696ea0f23dd3088e60898ed49dde7b913df8cbf436359a22e18cdb82c824fd700db65f652a0637 languageName: node linkType: hard @@ -146,6 +137,15 @@ __metadata: languageName: node linkType: hard +"@avalabs/core-coingecko-sdk@npm:3.1.0-alpha.2": + version: 3.1.0-alpha.2 + resolution: "@avalabs/core-coingecko-sdk@npm:3.1.0-alpha.2" + dependencies: + "@avalabs/core-utils-sdk": 3.1.0-alpha.2 + checksum: a31f5915718700e29a8c5f3f3624bfe94719130f6ffefc0adeabaa46594bae197f4e0944a9a7c9734cd7bd3a222ee74e74701c44c99133492a227063e7ad81c6 + languageName: node + linkType: hard + "@avalabs/core-etherscan-sdk@npm:3.1.0-alpha.1": version: 3.1.0-alpha.1 resolution: "@avalabs/core-etherscan-sdk@npm:3.1.0-alpha.1" @@ -159,21 +159,21 @@ __metadata: version: 0.0.0-use.local resolution: "@avalabs/core-mobile@workspace:packages/core-mobile" dependencies: - "@avalabs/avalanche-module": 0.1.10 + "@avalabs/avalanche-module": 0.1.11 "@avalabs/avalanchejs": 4.0.5 - "@avalabs/bitcoin-module": 0.1.10 + "@avalabs/bitcoin-module": 0.1.11 "@avalabs/bridge-unified": 2.1.0 - "@avalabs/core-bridge-sdk": 3.1.0-alpha.0 - "@avalabs/core-chains-sdk": 3.1.0-alpha.0 - "@avalabs/core-coingecko-sdk": 3.1.0-alpha.0 - "@avalabs/core-utils-sdk": 3.1.0-alpha.0 - "@avalabs/core-wallets-sdk": 3.1.0-alpha.0 - "@avalabs/evm-module": 0.1.10 - "@avalabs/glacier-sdk": 3.1.0-alpha.0 + "@avalabs/core-bridge-sdk": 3.1.0-alpha.2 + "@avalabs/core-chains-sdk": 3.1.0-alpha.2 + "@avalabs/core-coingecko-sdk": 3.1.0-alpha.2 + "@avalabs/core-utils-sdk": 3.1.0-alpha.2 + "@avalabs/core-wallets-sdk": 3.1.0-alpha.2 + "@avalabs/evm-module": 0.1.11 + "@avalabs/glacier-sdk": 3.1.0-alpha.2 "@avalabs/k2-mobile": "workspace:*" "@avalabs/tsconfig-mobile": "workspace:*" - "@avalabs/types": 3.1.0-alpha.0 - "@avalabs/vm-module-types": 0.1.10 + "@avalabs/types": 3.1.0-alpha.2 + "@avalabs/vm-module-types": 0.1.11 "@babel/core": 7.24.0 "@babel/plugin-proposal-nullish-coalescing-operator": 7.18.6 "@babel/plugin-syntax-object-rest-spread": 7.8.3 @@ -379,9 +379,9 @@ __metadata: languageName: unknown linkType: soft -"@avalabs/core-utils-sdk@npm:3.1.0-alpha.0": - version: 3.1.0-alpha.0 - resolution: "@avalabs/core-utils-sdk@npm:3.1.0-alpha.0" +"@avalabs/core-utils-sdk@npm:3.1.0-alpha.1": + version: 3.1.0-alpha.1 + resolution: "@avalabs/core-utils-sdk@npm:3.1.0-alpha.1" dependencies: "@avalabs/avalanchejs": 4.0.5 "@hpke/core": 1.2.5 @@ -390,13 +390,13 @@ __metadata: big.js: ^6.2.1 bn.js: ^5.2.1 ethers: ^6.7.1 - checksum: 51b77929a8373343395b5ed7262749e28c3708596c062bd2a5a98c2ce2396048c26d4fa9da377444f5958044bb924ad8f6ab1315ffed1ed4dafcbf47e56d1708 + checksum: b6a23252f27b7797287c963727cd2e34035850184909f935da04f2f06a0a4d2bdb0e64bba1a8f58add4a891c1c8a8908a0833d940c15e3d1885d849ea76b4981 languageName: node linkType: hard -"@avalabs/core-utils-sdk@npm:3.1.0-alpha.1": - version: 3.1.0-alpha.1 - resolution: "@avalabs/core-utils-sdk@npm:3.1.0-alpha.1" +"@avalabs/core-utils-sdk@npm:3.1.0-alpha.2": + version: 3.1.0-alpha.2 + resolution: "@avalabs/core-utils-sdk@npm:3.1.0-alpha.2" dependencies: "@avalabs/avalanchejs": 4.0.5 "@hpke/core": 1.2.5 @@ -405,17 +405,17 @@ __metadata: big.js: ^6.2.1 bn.js: ^5.2.1 ethers: ^6.7.1 - checksum: b6a23252f27b7797287c963727cd2e34035850184909f935da04f2f06a0a4d2bdb0e64bba1a8f58add4a891c1c8a8908a0833d940c15e3d1885d849ea76b4981 + checksum: 480dee728c3e3058f065710d55b5871a68b7f93f05fcfc28f7986bda8f3428f92ca5cf9dab5ebb782e24b01df0f3d9327bb2bbe49cc5a50cd9ffefafd0439cbb languageName: node linkType: hard -"@avalabs/core-wallets-sdk@npm:3.1.0-alpha.0": - version: 3.1.0-alpha.0 - resolution: "@avalabs/core-wallets-sdk@npm:3.1.0-alpha.0" +"@avalabs/core-wallets-sdk@npm:3.1.0-alpha.1": + version: 3.1.0-alpha.1 + resolution: "@avalabs/core-wallets-sdk@npm:3.1.0-alpha.1" dependencies: "@avalabs/avalanchejs": 4.0.5 - "@avalabs/core-chains-sdk": 3.1.0-alpha.0 - "@avalabs/glacier-sdk": 3.1.0-alpha.0 + "@avalabs/core-chains-sdk": 3.1.0-alpha.1 + "@avalabs/glacier-sdk": 3.1.0-alpha.1 "@avalabs/hw-app-avalanche": 0.14.1 "@ledgerhq/hw-app-btc": 10.2.4 "@ledgerhq/hw-app-eth": 6.36.1 @@ -433,17 +433,17 @@ __metadata: xss: 1.0.14 peerDependencies: ethers: ^6.7.1 - checksum: bb0f702ab026c4f267e2b301022503402bdeb17fe9d04d6d8676f257951b6067deb1b871ce12e42de594f3f10bc9270d97b87e12a6afd79bd8a750e4a36aa517 + checksum: aeb3a9d45038c88ff1805dd8019ebe3edab1f01c4cef2dbb00f48176ecef33685ccb219b775ef160faec8045a692eeba73cf31e54fbe415ce4d78fd5b93bd40c languageName: node linkType: hard -"@avalabs/core-wallets-sdk@npm:3.1.0-alpha.1": - version: 3.1.0-alpha.1 - resolution: "@avalabs/core-wallets-sdk@npm:3.1.0-alpha.1" +"@avalabs/core-wallets-sdk@npm:3.1.0-alpha.2": + version: 3.1.0-alpha.2 + resolution: "@avalabs/core-wallets-sdk@npm:3.1.0-alpha.2" dependencies: "@avalabs/avalanchejs": 4.0.5 - "@avalabs/core-chains-sdk": 3.1.0-alpha.1 - "@avalabs/glacier-sdk": 3.1.0-alpha.1 + "@avalabs/core-chains-sdk": 3.1.0-alpha.2 + "@avalabs/glacier-sdk": 3.1.0-alpha.2 "@avalabs/hw-app-avalanche": 0.14.1 "@ledgerhq/hw-app-btc": 10.2.4 "@ledgerhq/hw-app-eth": 6.36.1 @@ -461,13 +461,13 @@ __metadata: xss: 1.0.14 peerDependencies: ethers: ^6.7.1 - checksum: aeb3a9d45038c88ff1805dd8019ebe3edab1f01c4cef2dbb00f48176ecef33685ccb219b775ef160faec8045a692eeba73cf31e54fbe415ce4d78fd5b93bd40c + checksum: cd6898beca6fbcab1f50785782058beafe0593fd751a19e21a150bb876f6a29fcdf55fd8a1770a41d364925128244dccdbdce21e8299b1fe2f03e413337ef3be languageName: node linkType: hard -"@avalabs/evm-module@npm:0.1.10": - version: 0.1.10 - resolution: "@avalabs/evm-module@npm:0.1.10" +"@avalabs/evm-module@npm:0.1.11": + version: 0.1.11 + resolution: "@avalabs/evm-module@npm:0.1.11" dependencies: "@avalabs/core-coingecko-sdk": 3.1.0-alpha.1 "@avalabs/core-etherscan-sdk": 3.1.0-alpha.1 @@ -475,7 +475,7 @@ __metadata: "@avalabs/core-wallets-sdk": 3.1.0-alpha.1 "@avalabs/glacier-sdk": 3.1.0-alpha.1 "@avalabs/types": 3.1.0-alpha.1 - "@avalabs/vm-module-types": 0.1.10 + "@avalabs/vm-module-types": 0.1.11 "@blockaid/client": 0.11.0 "@metamask/rpc-errors": 6.3.0 "@zodios/core": 10.9.6 @@ -484,14 +484,7 @@ __metadata: zod: 3.23.8 peerDependencies: ethers: ^6.8.1 - checksum: d52e34a544767625cef2b05718abc452e5ee14aa8539cc212d1a22c376bfb81f8294bfcadd0175f3b0113d932688ae5b1d9d15de11959baf80b8ed77fdd2d791 - languageName: node - linkType: hard - -"@avalabs/glacier-sdk@npm:3.1.0-alpha.0": - version: 3.1.0-alpha.0 - resolution: "@avalabs/glacier-sdk@npm:3.1.0-alpha.0" - checksum: 179a51e9595ea0996bdf4fdc4ecf2caf7d9b3cbb7ef2790466f655b506236b8117fdbc3cef75df88a0f41d730056d4145b0acab2ff051eff32973e24d6390508 + checksum: 1a9ceb15346bf632d82cb7f0bd959bc09ed7f80cc8c49f70ef95cebf74eb133d7d8a6cf729641eb54c1e2db837583475ebe24281ab568e0828a5d110681672e0 languageName: node linkType: hard @@ -502,6 +495,13 @@ __metadata: languageName: node linkType: hard +"@avalabs/glacier-sdk@npm:3.1.0-alpha.2": + version: 3.1.0-alpha.2 + resolution: "@avalabs/glacier-sdk@npm:3.1.0-alpha.2" + checksum: b841c1615831c601d2cd49c8e4956a6d69f99d2cc943f9035a66f9fc201ca518b6a3ca9b6f75f2397a52cab2b4709b981decffd29e7dae1ea5ff8cab80f09238 + languageName: node + linkType: hard + "@avalabs/hw-app-avalanche@npm:0.14.1": version: 0.14.1 resolution: "@avalabs/hw-app-avalanche@npm:0.14.1" @@ -569,13 +569,6 @@ __metadata: languageName: unknown linkType: soft -"@avalabs/types@npm:3.1.0-alpha.0": - version: 3.1.0-alpha.0 - resolution: "@avalabs/types@npm:3.1.0-alpha.0" - checksum: fda29d1c69f2ae679ee73bbeb18b5d63936a5d4670ec5736fab694117913a07cede9dc786fe4d4e842543821127ce2c59b3e62a0df55ada2e00f0317ddb1b23a - languageName: node - linkType: hard - "@avalabs/types@npm:3.1.0-alpha.1": version: 3.1.0-alpha.1 resolution: "@avalabs/types@npm:3.1.0-alpha.1" @@ -583,9 +576,16 @@ __metadata: languageName: node linkType: hard -"@avalabs/vm-module-types@npm:0.1.10": - version: 0.1.10 - resolution: "@avalabs/vm-module-types@npm:0.1.10" +"@avalabs/types@npm:3.1.0-alpha.2": + version: 3.1.0-alpha.2 + resolution: "@avalabs/types@npm:3.1.0-alpha.2" + checksum: 6520f625f149db843b55a2da668501e792a824bb258584ac9c3fc888ceec5b8b9109c665e494e737f2f715404cc6b3e7efbc5bf231991da24b9a76f9e2a78906 + languageName: node + linkType: hard + +"@avalabs/vm-module-types@npm:0.1.11": + version: 0.1.11 + resolution: "@avalabs/vm-module-types@npm:0.1.11" dependencies: "@avalabs/core-wallets-sdk": 3.1.0-alpha.1 "@metamask/rpc-errors": 6.3.0 @@ -594,7 +594,7 @@ __metadata: zod: 3.23.8 peerDependencies: ethers: ^6.8.1 - checksum: 579c97576209f5e94fc3c85552846dfeff5a83a445441a285e6c0f40aca2c413c9c8178f41df74c1aba366a31ccf617248ffa6f307db4e50d0db814d2146bfce + checksum: ebfef28c5698295588825e9e29f52cc303bcddd43fb672dfb6ca4e210ef075a3199cca0c525a9f5f80712b65e1e2b5d809e3394e1ffb8c735f124ba26bd9772b languageName: node linkType: hard