From 6a556260b7b68699f0c71c9aa6958e3219b07f5b Mon Sep 17 00:00:00 2001 From: Corey Phillips Date: Fri, 16 Feb 2024 14:53:36 -0500 Subject: [PATCH] refactor(wallet): Update Types & Remove Methods Updates/Removes most types that have been migrated to Beignet. Updates/Removes most methods that have been migrated to Beignet. Added TODO's to several methods types that still need to be migrated into Beignet. --- src/screens/Activity/ActivityDetail.tsx | 2 +- src/screens/Activity/ActivityFiltered.tsx | 2 +- src/screens/Activity/ListItem.tsx | 3 +- .../AddressViewer/AddressViewerListItem.tsx | 2 +- src/screens/Settings/AddressViewer/index.tsx | 4 +- src/screens/Wallets/Send/CoinSelection.tsx | 2 +- src/store/actions/wallet.ts | 12 +- src/store/reselect/wallet.ts | 17 +- src/store/shapes/wallet.ts | 34 +- src/store/types/activity.ts | 2 +- src/store/types/checks.ts | 4 +- src/store/types/wallet.ts | 131 +-- src/store/utils/activity.ts | 2 +- src/store/utils/lightning.ts | 3 +- src/store/utils/metadata.ts | 2 +- src/utils/activity/index.ts | 3 +- src/utils/boost.ts | 38 +- src/utils/lightning/index.ts | 3 +- src/utils/types/index.ts | 22 +- src/utils/wallet/checks.ts | 21 +- src/utils/wallet/index.ts | 801 +----------------- src/utils/wallet/testing.ts | 2 +- src/utils/wallet/transactions.ts | 433 +--------- 23 files changed, 93 insertions(+), 1452 deletions(-) diff --git a/src/screens/Activity/ActivityDetail.tsx b/src/screens/Activity/ActivityDetail.tsx index e14765b68..54578c295 100644 --- a/src/screens/Activity/ActivityDetail.tsx +++ b/src/screens/Activity/ActivityDetail.tsx @@ -67,7 +67,6 @@ import useColors from '../../hooks/colors'; import { useAppDispatch, useAppSelector } from '../../hooks/redux'; import { showBottomSheet } from '../../store/utils/ui'; -import { EPaymentType, EBoostType } from '../../store/types/wallet'; import { activityItemSelector, activityItemsSelector, @@ -96,6 +95,7 @@ import type { import { i18nTime } from '../../utils/i18n'; import { useSwitchUnit } from '../../hooks/wallet'; import { contactsSelector } from '../../store/reselect/slashtags'; +import { EBoostType, EPaymentType } from 'beignet'; const Section = memo( ({ title, value }: { title: string; value: ReactNode }) => { diff --git a/src/screens/Activity/ActivityFiltered.tsx b/src/screens/Activity/ActivityFiltered.tsx index 7d17a830b..35d55997d 100644 --- a/src/screens/Activity/ActivityFiltered.tsx +++ b/src/screens/Activity/ActivityFiltered.tsx @@ -23,12 +23,12 @@ import useColors from '../../hooks/colors'; import { useAppDispatch } from '../../hooks/redux'; import { closeSheet } from '../../store/slices/ui'; import { showBottomSheet } from '../../store/utils/ui'; -import { EPaymentType } from '../../store/types/wallet'; import DetectSwipe from '../../components/DetectSwipe'; import type { WalletScreenProps } from '../../navigation/types'; import TimeRangePrompt from './TimeRangePrompt'; import TagsPrompt from './TagsPrompt'; import { TActivityFilter } from '../../utils/activity'; +import { EPaymentType } from 'beignet'; type TTab = { id: string; diff --git a/src/screens/Activity/ListItem.tsx b/src/screens/Activity/ListItem.tsx index 0b57c1723..3585df68d 100644 --- a/src/screens/Activity/ListItem.tsx +++ b/src/screens/Activity/ListItem.tsx @@ -22,11 +22,12 @@ import { import { useAppSelector } from '../../hooks/redux'; import { useProfile2 } from '../../hooks/slashtags2'; import { useFeeText } from '../../hooks/fees'; -import { EPaymentType, TTransferToSavings } from '../../store/types/wallet'; +import { TTransferToSavings } from '../../store/types/wallet'; import { slashTagsUrlSelector } from '../../store/reselect/metadata'; import { truncate } from '../../utils/helpers'; import { getActivityItemDate } from '../../utils/activity'; import { transferSelector } from '../../store/reselect/wallet'; +import { EPaymentType } from 'beignet'; export const ListItem = ({ title, diff --git a/src/screens/Settings/AddressViewer/AddressViewerListItem.tsx b/src/screens/Settings/AddressViewer/AddressViewerListItem.tsx index ba7720a53..a203bad97 100644 --- a/src/screens/Settings/AddressViewer/AddressViewerListItem.tsx +++ b/src/screens/Settings/AddressViewer/AddressViewerListItem.tsx @@ -1,10 +1,10 @@ import React, { ReactElement } from 'react'; import { StyleSheet, TextInputProps, View } from 'react-native'; -import { IAddress } from '../../../store/types/wallet'; import { TouchableOpacity } from '../../../styles/components'; import { Subtitle, Text01M } from '../../../styles/text'; import { Checkmark } from '../../../styles/icons'; import { IThemeColors } from '../../../styles/themes'; +import { IAddress } from 'beignet'; type ListItemProps = TextInputProps & { item: IAddress; diff --git a/src/screens/Settings/AddressViewer/index.tsx b/src/screens/Settings/AddressViewer/index.tsx index e600b854b..94ce882b2 100644 --- a/src/screens/Settings/AddressViewer/index.tsx +++ b/src/screens/Settings/AddressViewer/index.tsx @@ -36,7 +36,7 @@ import { selectedNetworkSelector, selectedWalletSelector, } from '../../../store/reselect/wallet'; -import { IAddress, IUtxo, TWalletName } from '../../../store/types/wallet'; +import { TWalletName } from '../../../store/types/wallet'; import Button from '../../../components/Button'; import { defaultAddressContent, @@ -69,7 +69,7 @@ import { setupLdk } from '../../../utils/lightning'; import { startWalletServices } from '../../../utils/startup'; import { updateOnchainFeeEstimates } from '../../../store/utils/fees'; import { viewControllerIsOpenSelector } from '../../../store/reselect/ui'; -import { EAddressType } from 'beignet'; +import { EAddressType, IAddress, IUtxo } from 'beignet'; export type TAddressViewerData = { [EAddressType.p2tr]: { diff --git a/src/screens/Wallets/Send/CoinSelection.tsx b/src/screens/Wallets/Send/CoinSelection.tsx index 72d62aa7e..49a7e9051 100644 --- a/src/screens/Wallets/Send/CoinSelection.tsx +++ b/src/screens/Wallets/Send/CoinSelection.tsx @@ -19,7 +19,6 @@ import { getTransactionOutputValue, } from '../../../utils/wallet/transactions'; import { addTxInput, removeTxInput } from '../../../store/actions/wallet'; -import { IUtxo } from '../../../store/types/wallet'; import type { SendScreenProps } from '../../../navigation/types'; import { transactionSelector, @@ -27,6 +26,7 @@ import { } from '../../../store/reselect/wallet'; import { coinSelectPreferenceSelector } from '../../../store/reselect/settings'; import { TRANSACTION_DEFAULTS } from '../../../utils/wallet/constants'; +import { IUtxo } from 'beignet'; /** * Some UTXO's may contain the same tx_hash. diff --git a/src/store/actions/wallet.ts b/src/store/actions/wallet.ts index ced34414a..5660afd7d 100644 --- a/src/store/actions/wallet.ts +++ b/src/store/actions/wallet.ts @@ -2,14 +2,9 @@ import { err, ok, Result } from '@synonymdev/result'; import actions from './actions'; import { - EBoostType, ETransferStatus, ETransferType, - IAddress, ICreateWallet, - IFormattedTransactions, - IKeyDerivationPath, - IUtxo, IWallets, IWalletStore, TTransfer, @@ -43,13 +38,18 @@ import { updateActivityList } from '../utils/activity'; import { EAddressType, EAvailableNetworks, + EBoostType, EFeeId, getDefaultWalletData, getStorageKeyValues, + IAddress, IBoostedTransaction, + IFormattedTransactions, + IKeyDerivationPath, IOnchainFees, IOutput, ISendTransaction, + IUtxo, IWalletData, TSetupTransactionResponse, } from 'beignet'; @@ -246,7 +246,7 @@ export const addUnconfirmedTransactions = ({ * FOR TESTING PURPOSES ONLY. DO NOT USE. * Injects a fake transaction into the store for testing. * @param {string} [id] - * @param {IFormattedTransaction} [fakeTx] + * @param {IFormattedTransactions} [fakeTx] * @param {boolean} [shouldRefreshWallet] * @param {TWalletName} [selectedWallet] * @param {EAvailableNetwork} [selectedNetwork] diff --git a/src/store/reselect/wallet.ts b/src/store/reselect/wallet.ts index 653b98ed9..a003e627d 100644 --- a/src/store/reselect/wallet.ts +++ b/src/store/reselect/wallet.ts @@ -1,16 +1,15 @@ -import { EAddressType, IFormattedTransaction, ISendTransaction } from 'beignet'; -import { createSelector } from '@reduxjs/toolkit'; - -import { RootState } from '..'; import { - IWalletStore, - IWallets, - IWallet, - TWalletName, + EAddressType, IBoostedTransactions, + IFormattedTransaction, IFormattedTransactions, + ISendTransaction, IUtxo, -} from '../types/wallet'; +} from 'beignet'; +import { createSelector } from '@reduxjs/toolkit'; + +import { RootState } from '..'; +import { IWalletStore, IWallets, IWallet, TWalletName } from '../types/wallet'; import { defaultSendTransaction } from '../shapes/wallet'; import { EAvailableNetwork } from '../../utils/networks'; import { IExchangeRates } from '../../utils/exchange-rate'; diff --git a/src/store/shapes/wallet.ts b/src/store/shapes/wallet.ts index 92838a673..a6be5d6a1 100644 --- a/src/store/shapes/wallet.ts +++ b/src/store/shapes/wallet.ts @@ -4,19 +4,18 @@ import { __WALLET_DEFAULT_SELECTED_NETWORK__ } from '../../constants/env'; import { IHeader } from '../../utils/types/electrum'; import { EAvailableNetwork } from '../../utils/networks'; import { objectKeys } from '../../utils/objectKeys'; +import { IWalletItem, IWallet, IWalletStore } from '../types/wallet'; import { - IWalletItem, - IWallet, - IWalletStore, - IKeyDerivationPath, - IAddressTypes, - IAddresses, - IAddress, + EAddressType, EBoostType, -} from '../types/wallet'; -import { EAddressType, EFeeId, ISendTransaction } from 'beignet'; + EFeeId, + IAddress, + IAddresses, + ISendTransaction, + TAddressTypes, +} from 'beignet'; -export const addressTypes: Readonly = { +export const addressTypes: Readonly = { [EAddressType.p2tr]: { type: EAddressType.p2tr, path: "m/86'/0'/0'/0/0", @@ -79,13 +78,6 @@ export type IAddressTypeContent = { [key in EAddressType]: T; }; -export type TAddressIndexInfo = { - addressIndex: IAddress; - changeAddressIndex: IAddress; - lastUsedAddressIndex: IAddress; - lastUsedChangeAddressIndex: IAddress; -}; - export const getAddressIndexShape = (): IWalletItem< IAddressTypeContent > => { @@ -140,14 +132,6 @@ export const defaultAddressContent: Readonly = { publicKey: '', }; -export const defaultKeyDerivationPath: Readonly = { - purpose: '84', - coinType: '0', - account: '0', - change: '0', - addressIndex: '0', -}; - export const defaultHeader: Readonly = { height: 0, hash: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', diff --git a/src/store/types/activity.ts b/src/store/types/activity.ts index 6c4370c2a..ae116def5 100644 --- a/src/store/types/activity.ts +++ b/src/store/types/activity.ts @@ -1,4 +1,4 @@ -import { EPaymentType } from './wallet'; +import { EPaymentType } from 'beignet'; export enum EActivityType { onchain = 'onchain', diff --git a/src/store/types/checks.ts b/src/store/types/checks.ts index bb985ec64..ef6e56556 100644 --- a/src/store/types/checks.ts +++ b/src/store/types/checks.ts @@ -1,7 +1,7 @@ -import { IAddress, IWalletItem, TWalletName } from './wallet'; +import { IWalletItem, TWalletName } from './wallet'; import { EAvailableNetwork } from '../../utils/networks'; import { TChannel } from '@synonymdev/react-native-ldk'; -import { EAddressType } from 'beignet'; +import { EAddressType, IAddress } from 'beignet'; export enum EWarningIds { 'storageCheck' = 888, diff --git a/src/store/types/wallet.ts b/src/store/types/wallet.ts index 721a32a9a..dca03a245 100644 --- a/src/store/types/wallet.ts +++ b/src/store/types/wallet.ts @@ -4,22 +4,16 @@ import { IAddressTypeContent } from '../shapes/wallet'; import { IHeader } from '../../utils/types/electrum'; import { EAddressType, - IFormattedTransaction, + IAddress, + IAddresses, + IBoostedTransactions, + IFormattedTransactions, ISendTransaction, + IUtxo, TServer, } from 'beignet'; -export enum EPaymentType { - sent = 'sent', - received = 'received', -} - export type TKeyDerivationAccountType = 'onchain'; -export type TKeyDerivationPurpose = '86' | '84' | '49' | '44'; //"p2tr" | "p2wpkh" | "p2sh" | "p2pkh"; -export type TKeyDerivationCoinType = '0' | '1'; //"mainnet" | "testnet"; -export type TKeyDerivationAccount = '0'; //"On-Chain Wallet"; -export type TKeyDerivationChange = '0' | '1'; //"Receiving Address" | "Change Address"; -export type TKeyDerivationAddressIndex = string; export type TAssetType = 'bitcoin' | 'tether'; export enum EConversionUnit { @@ -38,77 +32,6 @@ export enum EDenomination { classic = 'classic', } -export type TGetByteCountInput = - | `MULTISIG-P2SH:${number}-${number}` - | `MULTISIG-P2WSH:${number}-${number}` - | `MULTISIG-P2SH-P2WSH:${number}-${number}` - | 'P2SH-P2WPKH' - | 'P2PKH' - | 'p2pkh' - | 'P2WPKH' - | 'p2wpkh' - | 'P2SH' - | 'p2sh' - | 'P2TR' - | 'p2tr'; - -export type TGetByteCountOutput = - | 'P2SH' - | 'P2PKH' - | 'P2WPKH' - | 'P2WSH' - | 'p2wpkh' - | 'p2sh' - | 'p2pkh' - | 'P2TR' - | 'p2tr'; - -export type TGetByteCountInputs = { - [key in TGetByteCountInput]?: number; -}; - -export type TGetByteCountOutputs = { - [key in TGetByteCountOutput]?: number; -}; - -export enum EBoostType { - rbf = 'rbf', - cpfp = 'cpfp', -} - -export interface IAddressTypeData { - type: EAddressType; - path: string; - name: string; - shortName: string; - description: string; - example: string; -} - -export type IAddressTypes = { - [key in EAddressType]: Readonly; -}; - -// m / purpose' / coin_type' / account' / change / address_index -export interface IKeyDerivationPath { - purpose: TKeyDerivationPurpose; - coinType: TKeyDerivationCoinType; - account: TKeyDerivationAccount; - change: TKeyDerivationChange; - addressIndex: TKeyDerivationAddressIndex; -} - -export interface IKeyDerivationPathData { - pathString: string; - pathObject: IKeyDerivationPath; -} - -export type TProcessUnconfirmedTransactions = { - unconfirmedTxs: IFormattedTransactions; // zero-conf transactions - outdatedTxs: IUtxo[]; // Transactions that are no longer confirmed. - ghostTxs: string[]; // Transactions that have been removed from the mempool. -}; - export type TWalletName = `wallet${number}`; export interface IWalletStore { @@ -128,18 +51,6 @@ export interface IWalletItem { timestamp?: number | null; } -export interface IAddress { - index: number; - path: string; - address: string; - scriptHash: string; - publicKey: string; -} - -export interface IAddresses { - [scriptHash: string]: IAddress; -} - export interface ICreateWallet { walletName?: TWalletName; mnemonic: string; @@ -152,38 +63,6 @@ export interface ICreateWallet { servers?: TServer | TServer[]; } -export interface IUtxo { - address: string; - index: number; - path: string; - scriptHash: string; - height: number; - tx_hash: string; - tx_pos: number; - value: number; -} - -export interface IOutput { - address: string; // Address to send to. - value: number; // Amount denominated in sats. - index: number; // Used to specify which output to update or edit when using updateSendTransaction. -} - -export interface IFormattedTransactions { - [txId: string]: IFormattedTransaction; -} - -export interface IBoostedTransaction { - parentTransactions: string[]; // Array of parent txids to the currently boosted transaction. - childTransaction: string; // Child txid of the currently boosted transaction. - type: EBoostType; - fee: number; -} - -export interface IBoostedTransactions { - [txId: string]: IBoostedTransaction; -} - export type TTransfer = TTransferToSpending | TTransferToSavings; export enum ETransferType { diff --git a/src/store/utils/activity.ts b/src/store/utils/activity.ts index 12c6f5a7f..e5e79c1b1 100644 --- a/src/store/utils/activity.ts +++ b/src/store/utils/activity.ts @@ -1,7 +1,6 @@ import { ok, Result } from '@synonymdev/result'; import { TChannel } from '@synonymdev/react-native-ldk'; -import { EPaymentType } from '../types/wallet'; import { EActivityType, TLightningActivityItem } from '../types/activity'; import { getBlocktankStore, dispatch } from '../helpers'; import { getCurrentWallet } from '../../utils/wallet'; @@ -13,6 +12,7 @@ import { updateSettings } from '../slices/settings'; import { closeSheet } from '../slices/ui'; import { addActivityItem, updateActivityItems } from '../slices/activity'; import { showBottomSheet } from './ui'; +import { EPaymentType } from 'beignet'; /** * Attempts to determine if a given channel open was in response to diff --git a/src/store/utils/lightning.ts b/src/store/utils/lightning.ts index d6620e7fe..6bf560888 100644 --- a/src/store/utils/lightning.ts +++ b/src/store/utils/lightning.ts @@ -34,8 +34,9 @@ import { TCreateLightningInvoice, TLightningNodeVersion, } from '../types/lightning'; -import { EPaymentType, ETransferType, TWalletName } from '../types/wallet'; +import { ETransferType, TWalletName } from '../types/wallet'; import { EActivityType, TLightningActivityItem } from '../types/activity'; +import { EPaymentType } from 'beignet'; /** * Attempts to update the node id for the selected wallet and network. diff --git a/src/store/utils/metadata.ts b/src/store/utils/metadata.ts index 1663bd808..4340913ce 100644 --- a/src/store/utils/metadata.ts +++ b/src/store/utils/metadata.ts @@ -2,7 +2,7 @@ import { ok, Result } from '@synonymdev/result'; import { dispatch, getMetaDataStore } from '../helpers'; import { getCurrentWallet } from '../../utils/wallet'; import { moveMetaIncTxTag } from '../slices/metadata'; -import { EPaymentType } from '../types/wallet'; +import { EPaymentType } from 'beignet'; /** * Moves pending tags to metadata store linked to received transactions diff --git a/src/utils/activity/index.ts b/src/utils/activity/index.ts index 513760e8f..f38f15cab 100644 --- a/src/utils/activity/index.ts +++ b/src/utils/activity/index.ts @@ -1,11 +1,10 @@ import { err, ok, Result } from '@synonymdev/result'; -import { IFormattedTransaction } from 'beignet'; +import { EPaymentType, IFormattedTransaction } from 'beignet'; import { btcToSats } from '../conversion'; import { getCurrentWallet } from '../wallet'; import i18n, { i18nTime } from '../../utils/i18n'; import { getActivityStore } from '../../store/helpers'; -import { EPaymentType } from '../../store/types/wallet'; import { EActivityType, IActivityItem, diff --git a/src/utils/boost.ts b/src/utils/boost.ts index f0c11c27e..79b834eaf 100644 --- a/src/utils/boost.ts +++ b/src/utils/boost.ts @@ -2,11 +2,8 @@ import { getSelectedNetwork, getSelectedWallet } from './wallet'; import { EAvailableNetwork } from './networks'; import { getActivityStore, getWalletStore } from '../store/helpers'; import { IActivityItem, TOnchainActivityItem } from '../store/types/activity'; -import { - EBoostType, - IBoostedTransactions, - TWalletName, -} from '../store/types/wallet'; +import { TWalletName } from '../store/types/wallet'; +import { EBoostType, IBoostedTransactions } from 'beignet'; /** * Returns boosted transactions object. @@ -58,37 +55,9 @@ export const getBoostedTransactionParents = ({ return boostObj?.parentTransactions ?? []; }; -/** - * Determines if a given transaction was boosted via it's txid. - * CURRENTLY UNUSED - * @param {string} txid - * @param {IBoostedTransactions} [boostedTransactions] - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {boolean} - */ -export const isTransactionBoosted = ({ - txid, - boostedTransactions, - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), -}: { - txid: string; - boostedTransactions?: IBoostedTransactions; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): boolean => { - if (!boostedTransactions) { - boostedTransactions = getBoostedTransactions({ - selectedWallet, - selectedNetwork, - }); - } - return txid in boostedTransactions; -}; - /** * Determines if a given txid has any boosted parents. + * // TODO: Migrate to Beignet * @param {string} txid * @param {IBoostedTransactions} [boostedTransactions] * @param {TWalletName} [selectedWallet] @@ -161,6 +130,7 @@ export const getRootParentActivity = ({ /** * Returns an array of activity items for the provided array of parent txids. * CURRENTLY UNUSED + * // TODO: Migrate to Beignet * @param {string[]} [parents] * @param {IActivityItem[]} [items] */ diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts index b9df728d4..1f3debf5f 100644 --- a/src/utils/lightning/index.ts +++ b/src/utils/lightning/index.ts @@ -4,7 +4,7 @@ import * as bitcoin from 'bitcoinjs-lib'; import RNFS from 'react-native-fs'; import { err, ok, Result } from '@synonymdev/result'; import { TBroadcastTransaction } from '@synonymdev/react-native-ldk/dist/utils/types'; -import { TGetAddressHistory } from 'beignet'; +import { EPaymentType, TGetAddressHistory } from 'beignet'; import lm, { ldk, DefaultTransactionDataShape, @@ -74,7 +74,6 @@ import { import { addActivityItem } from '../../store/slices/activity'; import { addCJitActivityItem } from '../../store/utils/activity'; import { - EPaymentType, ETransferStatus, ETransferType, IWalletItem, diff --git a/src/utils/types/index.ts b/src/utils/types/index.ts index d450c406a..3f0279f81 100644 --- a/src/utils/types/index.ts +++ b/src/utils/types/index.ts @@ -1,11 +1,9 @@ import { EAvailableNetwork } from '../networks'; import { - IAddresses, - IKeyDerivationPath, TKeyDerivationAccountType, TWalletName, } from '../../store/types/wallet'; -import { EAddressType } from 'beignet'; +import { EAddressType, IKeyDerivationPath } from 'beignet'; export interface IResponse { error: boolean; @@ -18,19 +16,6 @@ export interface IGetAddress { selectedNetwork?: EAvailableNetwork; } -export interface IGetAddressResponse { - address: string; - path: string; - publicKey: string; -} - -export interface IGetInfoFromAddressPath { - error: boolean; - isChangeAddress?: boolean; - addressIndex?: number; - data?: string; -} - export interface IGenerateAddresses { selectedWallet?: TWalletName; addressAmount?: number; @@ -43,8 +28,3 @@ export interface IGenerateAddresses { addressType?: EAddressType; seed?: Buffer; } - -export interface IGenerateAddressesResponse { - addresses: IAddresses; - changeAddresses: IAddresses; -} diff --git a/src/utils/wallet/checks.ts b/src/utils/wallet/checks.ts index f09df06ad..031349c3a 100644 --- a/src/utils/wallet/checks.ts +++ b/src/utils/wallet/checks.ts @@ -1,9 +1,4 @@ -import { - IAddress, - IAddressTypeData, - IKeyDerivationPath, - TWalletName, -} from '../../store/types/wallet'; +import { TWalletName } from '../../store/types/wallet'; import { EAvailableNetwork } from '../networks'; import { err, ok, Result } from '@synonymdev/result'; import { getMinMaxObjects, TGetMinMaxObject } from '../helpers'; @@ -40,7 +35,12 @@ import { } from '../checks'; import { addressTypes } from '../../store/shapes/wallet'; import { dispatch } from '../../store/helpers'; -import { EAddressType } from 'beignet'; +import { + EAddressType, + IAddress, + IAddressTypeData, + IKeyDerivationPath, +} from 'beignet'; export const runChecks = async ({ selectedWallet = getSelectedWallet(), @@ -262,7 +262,6 @@ export const addressStorageCheck = async ({ /** * Generates specified addresses and formats them as needed for addressStorageCheck. * @param {TWalletName} selectedWallet - * @param {EAvailableNetwork} selectedNetwork * @param {EAddressType} addressType * @param {IKeyDerivationPath} keyDerivationPath * @param {TGetMinMaxObject} minMaxAddresses @@ -344,9 +343,9 @@ const _createMinMaxData = async ({ /** * Returns impacted addresses or stored addresses that do not match their generated counterparts. - * @param {TWalletName} selectedWallet] - * @param {EAvailableNetwork} selectedNetwork] - * @param {TMinMaxData[]} data + * @param {TWalletName} [selectedWallet] + * @param {EAvailableNetwork} [selectedNetwork] + * @param {TMinMaxData[]} storageCheckData * @returns {TGetImpactedAddressesRes} */ export const getImpactedAddresses = async ({ diff --git a/src/utils/wallet/index.ts b/src/utils/wallet/index.ts index 4a08d09e9..2e36fae07 100644 --- a/src/utils/wallet/index.ts +++ b/src/utils/wallet/index.ts @@ -9,35 +9,16 @@ import { err, ok, Result } from '@synonymdev/result'; import { EAvailableNetwork, networks } from '../networks'; import { - addressTypes, getDefaultWalletShape, getDefaultWalletStoreShape, - TAddressIndexInfo, } from '../../store/shapes/wallet'; import { - EPaymentType, - IAddress, - IAddresses, - IFormattedTransactions, - IKeyDerivationPath, - IKeyDerivationPathData, - IOutput, - IUtxo, IWallet, IWallets, - TKeyDerivationAccount, TKeyDerivationAccountType, - TKeyDerivationChange, - TKeyDerivationCoinType, - TKeyDerivationPurpose, TWalletName, } from '../../store/types/wallet'; -import { - IGetAddress, - IGenerateAddresses, - IGetAddressResponse, - IGetInfoFromAddressPath, -} from '../types'; +import { IGetAddress, IGenerateAddresses } from '../types'; import i18n from '../i18n'; import { btcToSats } from '../conversion'; import { getKeychainValue, setKeychainValue } from '../keychain'; @@ -58,33 +39,38 @@ import { } from '../../store/actions/wallet'; import { TCoinSelectPreference } from '../../store/types/settings'; import { updateActivityList } from '../../store/utils/activity'; -import { - getBlockHeader, - getTransactionsFromInputs, - TTxResult, -} from './electrum'; +import { getBlockHeader } from './electrum'; import { invokeNodeJsMethod } from '../nodejs-mobile'; import { DefaultNodeJsMethodsShape } from '../nodejs-mobile/shapes'; import { refreshLdk } from '../lightning'; -import { BITKIT_WALLET_SEED_HASH_PREFIX, CHUNK_LIMIT } from './constants'; +import { BITKIT_WALLET_SEED_HASH_PREFIX } from './constants'; import { moveMetaIncTxTags } from '../../store/utils/metadata'; import { refreshOrdersList } from '../../store/utils/blocktank'; import { TNode } from '../../store/types/lightning'; import { showNewOnchainTxPrompt, showNewTxPrompt } from '../../store/utils/ui'; import { promiseTimeout, reduceValue } from '../helpers'; -import { objectKeys } from '../objectKeys'; import { EAddressType, EAvailableNetworks, EElectrumNetworks, Electrum, getByteCount, + IAddress, ICustomGetAddress, IFormattedTransaction, + IFormattedTransactions, IGenerateAddressesResponse, + IGetAddressResponse, + IKeyDerivationPath, + IOutput, IRbfData, ISendTransaction, + IUtxo, IWalletData, + TKeyDerivationAccount, + TKeyDerivationChange, + TKeyDerivationCoinType, + TKeyDerivationPurpose, TOnMessage, Transaction, TTransactionMessage, @@ -265,92 +251,6 @@ export const getKeyDerivationAccount = ( ): TKeyDerivationAccount => { return keyDerivationAccountTypes[accountType]; }; -/** - * Formats and returns the provided derivation path string and object. - * @param {IKeyDerivationPath} path - * @param {TKeyDerivationPurpose | string} [purpose] - * @param {boolean} [changeAddress] - * @param {TKeyDerivationAccountType} [accountType] - * @param {string} [addressIndex] - * @param {EAvailableNetwork} [selectedNetwork] - * @return {Result<{IKeyDerivationPathData}>} Derivation Path Data - */ -export const formatKeyDerivationPath = ({ - path, - purpose, - selectedNetwork = getSelectedNetwork(), - accountType = 'onchain', - changeAddress = false, - addressIndex = '0', -}: { - path: IKeyDerivationPath | string; - purpose?: TKeyDerivationPurpose; - selectedNetwork?: EAvailableNetwork; - accountType?: TKeyDerivationAccountType; - changeAddress?: boolean; - addressIndex?: string; -}): Result => { - try { - if (typeof path === 'string') { - const derivationPathResponse = getKeyDerivationPathObject({ - path, - purpose, - selectedNetwork, - accountType, - changeAddress, - addressIndex, - }); - if (derivationPathResponse.isErr()) { - return err(derivationPathResponse.error.message); - } - path = derivationPathResponse.value; - } - const pathObject = path; - - const pathStringResponse = getKeyDerivationPathString({ - path: pathObject, - purpose, - selectedNetwork, - accountType, - changeAddress, - addressIndex, - }); - if (pathStringResponse.isErr()) { - return err(pathStringResponse.error.message); - } - const pathString = pathStringResponse.value; - return ok({ pathObject, pathString }); - } catch (e) { - return err(e); - } -}; - -/** - * Returns the derivation path object for the specified addressType and network. - * @param {EAddressType} addressType - * @param {EAvailableNetwork} [selectedNetwork] - * @returns Result - */ -export const getKeyDerivationPath = ({ - addressType, - selectedNetwork = getSelectedNetwork(), -}: { - addressType: EAddressType; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - const keyDerivationPathResponse = getKeyDerivationPathObject({ - selectedNetwork, - path: addressTypes[addressType].path, - }); - if (keyDerivationPathResponse.isErr()) { - return err(keyDerivationPathResponse.error.message); - } - return ok(keyDerivationPathResponse.value); - } catch (e) { - return err(e); - } -}; /** * Get onchain mnemonic phrase for a given wallet from storage. @@ -393,21 +293,6 @@ export const getBip39Passphrase = async ( } }; -export const getSeed = async ( - selectedWallet: TWalletName, -): Promise> => { - const getMnemonicPhraseResponse = await getMnemonicPhrase(selectedWallet); - if (getMnemonicPhraseResponse.isErr()) { - return err(getMnemonicPhraseResponse.error.message); - } - - //Attempt to acquire the bip39Passphrase if available - const bip39Passphrase = await getBip39Passphrase(selectedWallet); - - const mnemonic = getMnemonicPhraseResponse.value; - return ok(await bip39.mnemonicToSeed(mnemonic, bip39Passphrase)); -}; - /** * Get scriptHash for a given address * @param {string} address @@ -529,31 +414,6 @@ export const customGetAddress = async ({ } }; -/** - * Get info from an address path "m/49'/0'/0'/0/1" - * @param {string} path - The path to derive information from. - * @return {{error: , isChangeAddress: , addressIndex: , data: }} - */ -export const getInfoFromAddressPath = (path = ''): IGetInfoFromAddressPath => { - try { - if (path === '') { - return { error: true, data: 'No path specified' }; - } - let isChangeAddress = false; - const lastIndex = path.lastIndexOf('/'); - const addressIndex = Number(path.substr(lastIndex + 1)); - const firstIndex = path.lastIndexOf('/', lastIndex - 1); - const addressType = path.substr(firstIndex + 1, lastIndex - firstIndex - 1); - if (Number(addressType) === 1) { - isChangeAddress = true; - } - return { error: false, isChangeAddress, addressIndex }; - } catch (e) { - console.log(e); - return { error: true, isChangeAddress: false, addressIndex: 0, data: e }; - } -}; - /** * Determine if a given mnemonic is valid. * @param {string} mnemonic - The mnemonic to validate. @@ -584,169 +444,6 @@ export const getOnChainBalance = ({ return getWalletStore().wallets[selectedWallet]?.balance[selectedNetwork]; }; -/** - * - * @param {string} asset - * @return {string} - */ -export const getAssetTicker = (asset = 'bitcoin'): string => { - try { - switch (asset) { - case 'bitcoin': - return 'BTC'; - case 'bitcoinTestnet': - return 'BTC'; - default: - return ''; - } - } catch { - return ''; - } -}; - -/** - * This method will compare a set of specified addresses to the currently stored addresses and remove any duplicates. - * @param {IAddresses} addresses - * @param {IAddresses} changeAddresses - * @param {selectedWallet} selectedWallet - * @param {selectedNetwork} selectedNetwork - */ -export const removeDuplicateAddresses = async ({ - addresses = {}, - changeAddresses = {}, - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), -}: { - addresses?: IAddresses; - changeAddresses?: IAddresses; - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Promise> => { - try { - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - const currentAddressTypeContent = currentWallet.addresses[selectedNetwork]; - const currentChangeAddressTypeContent = - currentWallet.changeAddresses[selectedNetwork]; - - //Remove any duplicate addresses. - await Promise.all([ - objectKeys(currentAddressTypeContent).map(async (addressType) => { - await Promise.all( - objectKeys(addresses).map((scriptHash) => { - if (scriptHash in currentAddressTypeContent[addressType]) { - delete addresses[scriptHash]; - } - }), - ); - }), - - objectKeys(currentChangeAddressTypeContent).map(async (addressType) => { - await Promise.all( - objectKeys(changeAddresses).map((scriptHash) => { - if (scriptHash in currentChangeAddressTypeContent[addressType]) { - delete changeAddresses[scriptHash]; - } - }), - ); - }), - ]); - - return ok({ addresses, changeAddresses }); - } catch (e) { - return err(e); - } -}; - -interface ITxHashes extends TTxResult { - scriptHash: string; -} -interface IIndexes { - addressIndex: IAddress; - changeAddressIndex: IAddress; - foundAddressIndex: boolean; - foundChangeAddressIndex: boolean; -} - -export const getHighestUsedIndexFromTxHashes = ({ - txHashes, - addresses, - changeAddresses, - addressIndex, - changeAddressIndex, -}: { - txHashes: ITxHashes[]; - addresses: IAddresses; - changeAddresses: IAddresses; - addressIndex: IAddress; - changeAddressIndex: IAddress; -}): Result => { - try { - let foundAddressIndex = false; - let foundChangeAddressIndex = false; - - txHashes = txHashes.flat(); - txHashes.forEach(({ scriptHash }) => { - if ( - scriptHash in addresses && - addresses[scriptHash].index >= addressIndex.index - ) { - foundAddressIndex = true; - addressIndex = addresses[scriptHash]; - } else if ( - scriptHash in changeAddresses && - changeAddresses[scriptHash].index >= changeAddressIndex.index - ) { - foundChangeAddressIndex = true; - changeAddressIndex = changeAddresses[scriptHash]; - } - }); - - const data = { - addressIndex, - changeAddressIndex, - foundAddressIndex, - foundChangeAddressIndex, - }; - - return ok(data); - } catch (e) { - return err(e); - } -}; - -/** - * Returns the highest address and change address index stored in the app for the specified wallet and network. - */ -export const getHighestStoredAddressIndex = ({ - addressType, -}: { - addressType: EAddressType; -}): Result<{ - addressIndex: IAddress; - changeAddressIndex: IAddress; -}> => { - try { - const currentWallet = getOnChainWalletData(); - const addresses = currentWallet.addresses[addressType]; - const changeAddresses = currentWallet.changeAddresses[addressType]; - - const addressIndex = Object.values(addresses).reduce((prev, current) => { - return prev.index > current.index ? prev : current; - }); - - const changeAddressIndex = Object.values(changeAddresses).reduce( - (prev, current) => (prev.index > current.index ? prev : current), - ); - - return ok({ addressIndex, changeAddressIndex }); - } catch (e) { - return err(e); - } -}; - /** * Returns the currently selected network. * @return {EAvailableNetwork} @@ -886,234 +583,6 @@ export interface ITxHash { tx_hash: string; } -type InputData = { - [key: string]: { - addresses: string[]; - value: number; - }; -}; - -export const getInputData = async ({ - inputs, -}: { - inputs: { tx_hash: string; vout: number }[]; - selectedNetwork?: EAvailableNetwork; -}): Promise> => { - try { - const inputData: InputData = {}; - - for (let i = 0; i < inputs.length; i += CHUNK_LIMIT) { - const chunk = inputs.slice(i, i + CHUNK_LIMIT); - - const getTransactionsResponse = await getTransactionsFromInputs({ - txHashes: chunk, - }); - if (getTransactionsResponse.isErr()) { - return err(getTransactionsResponse.error.message); - } - getTransactionsResponse.value.data.map(({ data, result }) => { - const vout = result.vout[data.vout]; - const addresses = vout.scriptPubKey.addresses - ? vout.scriptPubKey.addresses - : vout.scriptPubKey.address - ? [vout.scriptPubKey.address] - : []; - const value = vout.value; - const key = `${data.tx_hash}${vout.n}`; - inputData[key] = { addresses, value }; - }); - } - return ok(inputData); - } catch (e) { - return err(e); - } -}; - -export const formatTransactions = async ({ - transactions, - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), -}: { - transactions: ITransaction[]; - selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; -}): Promise> => { - if (transactions.length < 1) { - return ok({}); - } - const { currentWallet } = getCurrentWallet({ - selectedNetwork, - selectedWallet, - }); - - // Batch and pre-fetch input data. - const inputs: { tx_hash: string; vout: number }[] = []; - transactions.forEach(({ result }) => { - result.vin.forEach((v) => inputs.push({ tx_hash: v.txid, vout: v.vout })); - }); - const inputDataResponse = await getInputData({ - selectedNetwork, - inputs, - }); - if (inputDataResponse.isErr()) { - return err(inputDataResponse.error.message); - } - const addressTypeKeys = objectKeys(EAddressType); - const inputData = inputDataResponse.value; - const currentAddresses = currentWallet.addresses[selectedNetwork]; - const currentChangeAddresses = currentWallet.changeAddresses[selectedNetwork]; - - let addresses = {} as IAddresses; - let changeAddresses = {} as IAddresses; - - addressTypeKeys.map((addressType) => { - // Check if addresses of this type have been generated. If not, skip. - if (Object.keys(currentAddresses[addressType])?.length > 0) { - addresses = { - ...addresses, - ...currentAddresses[addressType], - }; - } - // Check if change addresses of this type have been generated. If not, skip. - if (Object.keys(currentChangeAddresses[addressType])?.length > 0) { - changeAddresses = { - ...changeAddresses, - ...currentChangeAddresses[addressType], - }; - } - }); - - // Create combined address/change-address object for easier/faster reference later on. - const combinedAddressObj: { [key: string]: IAddress } = {}; - [...Object.values(addresses), ...Object.values(changeAddresses)].map( - (data) => { - combinedAddressObj[data.address] = data; - }, - ); - - const formattedTransactions: IFormattedTransactions = {}; - transactions.map(async ({ data, result }) => { - if (!result.txid) { - return; - } - - let totalInputValue = 0; // Total value of all inputs. - let matchedInputValue = 0; // Total value of all inputs with addresses that belong to this wallet. - let totalOutputValue = 0; // Total value of all outputs. - let matchedOutputValue = 0; // Total value of all outputs with addresses that belong to this wallet. - let messages: string[] = []; // Array of OP_RETURN messages. - - //Iterate over each input - result.vin.map(({ txid, scriptSig, vout }) => { - //Push any OP_RETURN messages to messages array - try { - const asm = scriptSig.asm; - if (asm !== '' && asm.includes('OP_RETURN')) { - const OpReturnMessages = decodeOpReturnMessage(asm); - messages = messages.concat(OpReturnMessages); - } - } catch {} - - const { addresses: _addresses, value } = inputData[`${txid}${vout}`]; - totalInputValue = totalInputValue + value; - _addresses.map((address) => { - if (address in combinedAddressObj) { - matchedInputValue = matchedInputValue + value; - } - }); - }); - - //Iterate over each output - result.vout.map(({ scriptPubKey, value }) => { - const _addresses = scriptPubKey.addresses - ? scriptPubKey.addresses - : scriptPubKey.address - ? [scriptPubKey.address] - : []; - totalOutputValue = totalOutputValue + value; - _addresses.map((address) => { - if (address in combinedAddressObj) { - matchedOutputValue = matchedOutputValue + value; - } - }); - }); - - const txid = result.txid; - const type = - matchedInputValue > matchedOutputValue - ? EPaymentType.sent - : EPaymentType.received; - const totalMatchedValue = matchedOutputValue - matchedInputValue; - const value = Number(totalMatchedValue.toFixed(8)); - const totalValue = totalInputValue - totalOutputValue; - const fee = Number(Math.abs(totalValue).toFixed(8)); - const vsize = result.vsize; - const satsPerByte = btcToSats(fee) / vsize; - const { address, height, scriptHash } = data; - let timestamp = Date.now(); - let confirmTimestamp: number | undefined; - - if (height > 0 && result.blocktime) { - confirmTimestamp = result.blocktime * 1000; - //In the event we're recovering, set the older timestamp. - if (confirmTimestamp < timestamp) { - timestamp = confirmTimestamp; - } - } - - formattedTransactions[txid] = { - address, - height, - scriptHash, - totalInputValue, - matchedInputValue, - totalOutputValue, - matchedOutputValue, - fee, - satsPerByte, - type, - value, - txid, - messages, - timestamp, - confirmTimestamp, - vsize, - vin: result.vin, - }; - }); - - return ok(formattedTransactions); -}; - -//Returns an array of messages from an OP_RETURN message -export const decodeOpReturnMessage = (opReturn = ''): string[] => { - let messages: string[] = []; - try { - //Remove OP_RETURN from the string & trim the string. - if (opReturn.includes('OP_RETURN')) { - opReturn = opReturn.replace('OP_RETURN', ''); - opReturn = opReturn.trim(); - } - - const regex = /[0-9A-Fa-f]{6}/g; - //Separate the string into an array based upon a space and insert each message into an array to be returned - const data = opReturn.split(' '); - data.forEach((msg) => { - try { - //Ensure the message is in fact a hex - if (regex.test(msg)) { - const message = Buffer.from(msg, 'hex').toString(); - messages.push(message); - } - } catch {} - }); - return messages; - } catch (e) { - console.log(e); - return messages; - } -}; - export const getCustomElectrumPeers = ({ selectedNetwork = getSelectedNetwork(), }: { @@ -1545,6 +1014,7 @@ export interface ICoinSelectResponse { /** * This method will do its best to select only the necessary inputs that are provided base on the selected sortMethod. + * // TODO: Migrate to Beignet * @param {IUtxo[]} [inputs] * @param {IUtxo[]} [outputs] * @param {number} [satsPerByte] @@ -1679,60 +1149,6 @@ export const autoCoinSelect = async ({ } }; -/** - * Parses a key derivation path object and returns it in string format. Ex: "m/84'/0'/0'/0/0" - * @param {IKeyDerivationPath} path - * @param {TKeyDerivationPurpose | string} [purpose] - * @param {boolean} [changeAddress] - * @param {TKeyDerivationAccountType} [accountType] - * @param {string} [addressIndex] - * @param {EAvailableNetwork} [selectedNetwork] - * @return {Result} - */ -export const getKeyDerivationPathString = ({ - path, - purpose, - accountType, - changeAddress, - addressIndex = '0', - selectedNetwork, -}: { - path: IKeyDerivationPath; - purpose?: TKeyDerivationPurpose; - accountType?: TKeyDerivationAccountType; - changeAddress?: boolean; - addressIndex?: string; - selectedNetwork?: EAvailableNetwork; -}): Result => { - try { - if (!path) { - return err('No path specified.'); - } - //Specifically specifying purpose will override the default accountType purpose value. - if (purpose) { - path.purpose = purpose; - } - - if (selectedNetwork) { - path.coinType = - selectedNetwork.toLocaleLowerCase() === EAvailableNetworks.bitcoin - ? '0' - : '1'; - } - if (accountType) { - path.account = getKeyDerivationAccount(accountType); - } - if (changeAddress !== undefined) { - path.change = changeAddress ? '1' : '0'; - } - return ok( - `m/${path.purpose}'/${path.coinType}'/${path.account}'/${path.change}/${addressIndex}`, - ); - } catch (e) { - return err(e); - } -}; - /** * Parses a key derivation path in string format Ex: "m/84'/0'/0'/0/0" and returns IKeyDerivationPath. * @param {string} keyDerivationPath @@ -1799,49 +1215,6 @@ export const getKeyDerivationPathObject = ({ } }; -/** - * The method returns the base key derivation path for a given address type. - * @param {EAddressType} [addressType] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {TWalletName} [selectedWallet] - * @param {boolean} [changeAddress] - * @return {Result<{ pathString: string, pathObject: IKeyDerivationPath }>} - */ -export const getAddressTypePath = ({ - addressType, - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), - changeAddress, -}: { - addressType?: EAddressType; - selectedNetwork?: EAvailableNetwork; - selectedWallet?: TWalletName; - changeAddress?: boolean; -}): Result => { - try { - if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - - const path = addressTypes[addressType].path; - const pathData = formatKeyDerivationPath({ - path, - selectedNetwork, - changeAddress, - }); - if (pathData.isErr()) { - return err(pathData.error.message); - } - - return ok({ - pathString: pathData.value.pathString, - pathObject: pathData.value.pathObject, - }); - } catch (e) { - return err(e); - } -}; - /** * Returns the next available receive address for the given network and wallet. * @param {EAddressType} [addressType] @@ -1974,108 +1347,6 @@ export const getBalance = ({ }; }; -/** - * Returns the difference between the current address index and the last used address index. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {EAddressType} [addressType] - * @returns {Result<{ addressDelta: number; changeAddressDelta: number }>} - */ -export const getGapLimit = ({ - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), - addressType, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - addressType?: EAddressType; -}): Result<{ addressDelta: number; changeAddressDelta: number }> => { - try { - if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - const addressIndex = - currentWallet.addressIndex[selectedNetwork][addressType].index; - const lastUsedAddressIndex = - currentWallet.lastUsedAddressIndex[selectedNetwork][addressType].index; - const changeAddressIndex = - currentWallet.changeAddressIndex[selectedNetwork][addressType].index; - const lastUsedChangeAddressIndex = - currentWallet.lastUsedChangeAddressIndex[selectedNetwork][addressType] - .index; - const addressDelta = Math.abs( - addressIndex - (lastUsedAddressIndex > 0 ? lastUsedAddressIndex : 0), - ); - const changeAddressDelta = Math.abs( - changeAddressIndex - - (lastUsedChangeAddressIndex > 0 ? lastUsedChangeAddressIndex : 0), - ); - - return ok({ addressDelta, changeAddressDelta }); - } catch (e) { - console.log(e); - return err(e); - } -}; - -/** - * Get address for a given scriptPubKey. - * @param scriptPubKey - * @param selectedNetwork - * @returns {string} - */ -export const getAddressFromScriptPubKey = ( - scriptPubKey: string, - selectedNetwork: EAvailableNetwork = getSelectedNetwork(), -): string => { - const network = networks[selectedNetwork]; - return bitcoin.address.fromOutputScript( - Buffer.from(scriptPubKey, 'hex'), - network, - ); -}; - -/** - * Returns current address index information. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @param {EAddressType} [addressType] - */ -export const getAddressIndexInfo = ({ - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), - addressType, -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; - addressType?: EAddressType; -}): TAddressIndexInfo => { - if (!addressType) { - addressType = getSelectedAddressType({ selectedNetwork, selectedWallet }); - } - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - const addressIndex = currentWallet.addressIndex[selectedNetwork][addressType]; - const changeAddressIndex = - currentWallet.addressIndex[selectedNetwork][addressType]; - const lastUsedAddressIndex = - currentWallet.lastUsedAddressIndex[selectedNetwork][addressType]; - const lastUsedChangeAddressIndex = - currentWallet.lastUsedChangeAddressIndex[selectedNetwork][addressType]; - return { - addressIndex, - changeAddressIndex, - lastUsedAddressIndex, - lastUsedChangeAddressIndex, - }; -}; - /** * This method will clear the utxo array for each address type and reset the * address indexes back to the original/default app values. Once cleared & reset @@ -2097,27 +1368,6 @@ export const rescanAddresses = async ({ }); }; -/** - * Returns the current wallet's unconfirmed transactions. - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] - * @returns {Promise>} - */ -export const getUnconfirmedTransactions = async ({ - selectedWallet = getSelectedWallet(), - selectedNetwork = getSelectedNetwork(), -}: { - selectedWallet?: TWalletName; - selectedNetwork?: EAvailableNetwork; -}): Promise> => { - const { currentWallet } = getCurrentWallet({ - selectedWallet, - selectedNetwork, - }); - - return ok(currentWallet?.unconfirmedTransactions[selectedNetwork] ?? {}); -}; - /** * Returns the number of confirmations for a given block height. * @param {number} height @@ -2144,29 +1394,6 @@ export const blockHeightToConfirmations = ({ return currentHeight - blockHeight + 1; }; -/** - * Returns the block height for a given number of confirmations. - * @param {number} confirmations - * @param {number} [currentHeight] - * @returns {number} - */ -export const confirmationsToBlockHeight = ({ - confirmations, - currentHeight, -}: { - confirmations: number; - currentHeight?: number; -}): number => { - if (!currentHeight) { - const header = getBlockHeader(); - currentHeight = header.height; - } - if (confirmations > currentHeight) { - return 0; - } - return currentHeight - confirmations; -}; - export const getOnChainWallet = (): Wallet => { return wallet; }; diff --git a/src/utils/wallet/testing.ts b/src/utils/wallet/testing.ts index 7eb2ee14c..f62b20126 100644 --- a/src/utils/wallet/testing.ts +++ b/src/utils/wallet/testing.ts @@ -1,4 +1,4 @@ -import { EPaymentType, IFormattedTransactions } from '../../store/types/wallet'; +import { EPaymentType, IFormattedTransactions } from 'beignet'; /** * Used to generate a fake transaction for testing purposes. diff --git a/src/utils/wallet/transactions.ts b/src/utils/wallet/transactions.ts index c5cf14b19..75e4ad841 100644 --- a/src/utils/wallet/transactions.ts +++ b/src/utils/wallet/transactions.ts @@ -1,29 +1,14 @@ -import ecc from '@bitcoinerlab/secp256k1'; -import { BIP32Factory, BIP32Interface } from 'bip32'; -import * as bip39 from 'bip39'; -import * as bitcoin from 'bitcoinjs-lib'; -import { Psbt } from 'bitcoinjs-lib'; import { err, ok, Result } from '@synonymdev/result'; import validate, { getAddressInfo } from 'bitcoin-address-validation'; -import { __E2E__, __JEST__ } from '../../constants/env'; -import { networks, EAvailableNetwork } from '../networks'; -import { reduceValue, shuffleArray } from '../helpers'; +import { __E2E__ } from '../../constants/env'; +import { EAvailableNetwork } from '../networks'; +import { reduceValue } from '../helpers'; import { btcToSats, satsToBtc } from '../conversion'; -import { getKeychainValue } from '../keychain'; -import { - EBoostType, - EPaymentType, - IOutput, - IUtxo, - TGetByteCountInputs, - TGetByteCountOutputs, - TWalletName, -} from '../../store/types/wallet'; +import { TWalletName } from '../../store/types/wallet'; import { getBalance, getCurrentWallet, - getMnemonicPhrase, getOnChainBalance, getOnChainWallet, getOnChainWalletElectrum, @@ -43,7 +28,6 @@ import { import { addBoostedTransaction, deleteOnChainTransactionById, - getChangeAddress, setupOnChainTransaction, updateSendTransaction, } from '../../store/actions/wallet'; @@ -52,50 +36,25 @@ import { TCoinSelectPreference, } from '../../store/types/settings'; import { showToast } from '../notifications'; -import { getTransactions, subscribeToAddresses } from './electrum'; +import { subscribeToAddresses } from './electrum'; import { TOnchainActivityItem } from '../../store/types/activity'; import { initialFeesState } from '../../store/slices/fees'; import { TRANSACTION_DEFAULTS } from './constants'; import i18n from '../i18n'; import { EAddressType, + EBoostType, EFeeId, + EPaymentType, getByteCount, IOnchainFees, + IOutput, ISendTransaction, + IUtxo, + TGetByteCountInputs, + TGetByteCountOutputs, } from 'beignet'; -bitcoin.initEccLib(ecc); -const bip32 = BIP32Factory(ecc); - -const setReplaceByFee = ({ - psbt, - setRbf = true, -}: { - psbt: Psbt; - setRbf: boolean; -}): void => { - try { - const defaultSequence = bitcoin.Transaction.DEFAULT_SEQUENCE; - //Cannot set replace-by-fee on transaction without inputs. - // @ts-ignore type for Psbt is wrong - const ins = psbt.data.globalMap.unsignedTx.tx.ins; - if (ins.length !== 0) { - ins.forEach((x) => { - if (setRbf) { - if (x.sequence >= defaultSequence - 1) { - x.sequence = 0; - } - } else { - if (x.sequence < defaultSequence - 1) { - x.sequence = defaultSequence; - } - } - }); - } - } catch (e) {} -}; - /** * Constructs the parameter for getByteCount via an array of addresses. * @param {string[]} addresses @@ -179,257 +138,6 @@ interface ICreateTransaction { transactionData?: ISendTransaction; } -/** - * Creates a BIP32Interface from the selected wallet's mnemonic and passphrase - * @param {TWalletName} selectedWallet - * @param {EAvailableNetwork} selectedNetwork - * @returns {Promise>} - */ -const getBip32Interface = async ( - selectedWallet: TWalletName, - selectedNetwork: EAvailableNetwork, -): Promise> => { - const network = networks[selectedNetwork]; - - const getMnemonicPhraseResult = await getMnemonicPhrase(selectedWallet); - if (getMnemonicPhraseResult.isErr()) { - return err(getMnemonicPhraseResult.error.message); - } - - //Attempt to acquire the bip39Passphrase if available - let bip39Passphrase = ''; - try { - const key = `${selectedWallet}passphrase`; - const bip39PassphraseResult = await getKeychainValue({ key }); - if (!bip39PassphraseResult.error && bip39PassphraseResult.data) { - bip39Passphrase = bip39PassphraseResult.data; - } - } catch {} - - const mnemonic = getMnemonicPhraseResult.value; - const seed = await bip39.mnemonicToSeed(mnemonic, bip39Passphrase); - const root = bip32.fromSeed(seed, network); - - return ok(root); -}; - -interface ITargets { - value: number; // Amount denominated in sats. - index: number; // Used to specify which output to update or edit when using updateSendTransaction. - address?: string; // Amount denominated in sats. - script?: Buffer; -} - -/** - * Returns a PSBT that includes unsigned funding inputs. - * @param {TWalletName} selectedWallet - * @param {EAvailableNetwork} selectedNetwork - * @param {ISendTransaction} transactionData - * @param {BIP32Interface} bip32Interface - * @return {Promise>} - */ -const createPsbtFromTransactionData = async ({ - selectedWallet, - selectedNetwork, - transactionData, - bip32Interface, -}: { - selectedWallet: TWalletName; - selectedNetwork: EAvailableNetwork; - transactionData: ISendTransaction; - bip32Interface?: BIP32Interface; -}): Promise> => { - const { inputs, outputs, fee, rbf } = transactionData; - let { changeAddress, message } = transactionData; - - //Get balance of current inputs. - const balance = getTransactionInputValue({ - inputs, - }); - - //Get value of current outputs. - const outputValue = getTransactionOutputValue({ - outputs, - }); - - const network = networks[selectedNetwork]; - - //Collect all outputs. - let targets: ITargets[] = outputs.concat(); - - //Change address and amount to send back to wallet. - if (changeAddress) { - const changeAddressValue = balance - (outputValue + fee); - // Ensure we're not creating unspendable dust. - // If we have less than 2x the recommended base fee, just contribute it to the fee in this transaction. - if (changeAddressValue > TRANSACTION_DEFAULTS.dustLimit) { - targets.push({ - address: changeAddress, - value: changeAddressValue, - index: targets.length, - }); - } - // Looks like we don't need a change address. - // Double check we don't have any spare sats hanging around. - } else if (outputValue + fee < balance) { - // If we have spare sats hanging around and the difference is greater than the dust limit, generate a changeAddress to send them to. - const diffValue = balance - (outputValue + fee); - if (diffValue > TRANSACTION_DEFAULTS.dustLimit) { - const changeAddressRes = await getChangeAddress({}); - if (changeAddressRes.isErr()) { - return err(changeAddressRes.error.message); - } - changeAddress = changeAddressRes.value.address; - targets.push({ - address: changeAddress, - value: diffValue, - index: targets.length, - }); - } - } - - //Embed any OP_RETURN messages. - if (message.trim() !== '') { - const messageLength = message.length; - const lengthMin = 5; - //This is a patch for the following: https://github.com/coreyphillips/moonshine/issues/52 - if (messageLength > 0 && messageLength < lengthMin) { - message += ' '.repeat(lengthMin - messageLength); - } - const data = Buffer.from(message, 'utf8'); - const embed = bitcoin.payments.embed({ - data: [data], - network, - }); - targets.push({ script: embed.output!, value: 0, index: targets.length }); - } - - if (!bip32Interface) { - const bip32InterfaceRes = await getBip32Interface( - selectedWallet, - selectedNetwork, - ); - if (bip32InterfaceRes.isErr()) { - return err(bip32InterfaceRes.error.message); - } - bip32Interface = bip32InterfaceRes.value; - } - - const root = bip32Interface; - const psbt = new bitcoin.Psbt({ network }); - - //Add Inputs from inputs array - try { - for (const input of inputs) { - const path = input.path; - const keyPair: BIP32Interface = root.derivePath(path); - await addInput({ - psbt, - keyPair, - input, - selectedNetwork, - }); - } - } catch (e) { - return err(e); - } - - //Set RBF if supported and prompted via rbf in Settings. - setReplaceByFee({ psbt, setRbf: !!rbf }); - - // Shuffle targets if not run from unit test and add outputs. - if (!__JEST__) { - targets = shuffleArray(targets); - } - - targets.forEach((target) => { - //Check if OP_RETURN - let isOpReturn = false; - try { - isOpReturn = !!target.script; - } catch (e) {} - if (isOpReturn) { - if (target.script) { - psbt.addOutput({ - script: target.script, - value: target.value ?? 0, - }); - } - } else { - if (target.address && target.value) { - psbt.addOutput({ - address: target.address, - value: target.value, - }); - } - } - }); - - return ok(psbt); -}; - -/** - * Uses the transaction data store to create an unsigned PSBT with funded inputs - * CURRENTLY UNUSED - * @param {TWalletName} selectedWallet - * @param {EAvailableNetwork} selectedNetwork - */ -export const createFundedPsbtTransaction = async ({ - selectedWallet, - selectedNetwork, -}: { - selectedWallet: TWalletName; - selectedNetwork: EAvailableNetwork; -}): Promise> => { - const transactionData = getOnchainTransactionData(); - - if (transactionData.isErr()) { - return err(transactionData.error.message); - } - - //Create PSBT before signing inputs - return await createPsbtFromTransactionData({ - selectedWallet, - selectedNetwork, - transactionData: transactionData.value, - }); -}; - -/** - * Loops through inputs and signs them - * @param {Psbt} psbt - * @param {BIP32Interface} bip32Interface - * @param {TWalletName} selectedWallet - * @param {EAvailableNetwork} selectedNetwork - * @returns {Promise>} - */ -export const signPsbt = async ({ - psbt, - bip32Interface, -}: { - psbt: Psbt; - bip32Interface: BIP32Interface; -}): Promise> => { - const transactionDataRes = getOnchainTransactionData(); - if (transactionDataRes.isErr()) { - return err(transactionDataRes.error.message); - } - - const { inputs } = transactionDataRes.value; - for (const [index, input] of inputs.entries()) { - try { - const keyPair = bip32Interface.derivePath(input.path); - psbt.signInput(index, keyPair); - } catch (e) { - return err(e); - } - } - - psbt.finalizeAllInputs(); - - return ok(psbt); -}; - /** * Creates complete signed transaction using the transaction data store * @param {ISendTransaction} [transactionData] @@ -457,17 +165,6 @@ export const createTransaction = async ({ } }; -/** - * Removes outputs that are below the dust limit. - * @param {IOutput[]} outputs - * @returns {IOutput[]} - */ -export const removeDustOutputs = (outputs: IOutput[]): IOutput[] => { - return outputs.filter((output) => { - return output.value > TRANSACTION_DEFAULTS.dustLimit; - }); -}; - /** * Returns onchain transaction data related to the specified network and wallet. * @returns {Result} @@ -483,87 +180,6 @@ export const getOnchainTransactionData = (): Result => { return err(e); } }; -export interface IAddInput { - psbt: Psbt; - keyPair: BIP32Interface; - input: IUtxo; - selectedNetwork?: EAvailableNetwork; -} -export const addInput = async ({ - psbt, - keyPair, - input, - selectedNetwork = getSelectedNetwork(), -}: IAddInput): Promise> => { - try { - const network = networks[selectedNetwork]; - const { type } = getAddressInfo(input.address); - - if (!input.value) { - return err('No input provided.'); - } - - if (type === 'p2wpkh') { - const p2wpkh = bitcoin.payments.p2wpkh({ - pubkey: keyPair.publicKey, - network, - }); - if (!p2wpkh?.output) { - return err('p2wpkh.output is undefined.'); - } - psbt.addInput({ - hash: input.tx_hash, - index: input.tx_pos, - witnessUtxo: { - script: p2wpkh.output, - value: input.value, - }, - }); - } - - if (type === 'p2sh') { - const p2wpkh = bitcoin.payments.p2wpkh({ - pubkey: keyPair.publicKey, - network, - }); - const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network }); - if (!p2sh?.output) { - return err('p2sh.output is undefined.'); - } - if (!p2sh?.redeem) { - return err('p2sh.redeem.output is undefined.'); - } - psbt.addInput({ - hash: input.tx_hash, - index: input.tx_pos, - witnessUtxo: { - script: p2sh.output, - value: input.value, - }, - redeemScript: p2sh.redeem.output, - }); - } - - if (type === 'p2pkh') { - const transaction = await getTransactions({ - txHashes: [{ tx_hash: input.tx_hash }], - }); - if (transaction.isErr()) { - return err(transaction.error.message); - } - const hex = transaction.value.data[0].result.hex; - const nonWitnessUtxo = Buffer.from(hex, 'hex'); - psbt.addInput({ - hash: input.tx_hash, - index: input.tx_pos, - nonWitnessUtxo, - }); - } - return ok('Success'); - } catch { - return err('Unable to add input.'); - } -}; export const broadcastTransaction = async ({ rawTx, @@ -713,7 +329,7 @@ export const getBlockExplorerLink = ( } }; -export interface IAddressTypes { +export interface IAddressTypesIO { inputs: { [key in EAddressType]: number; }; @@ -721,20 +337,13 @@ export interface IAddressTypes { [key in EAddressType]: number; }; } -/** - * Returns the transaction fee and outputs along with the inputs that best fit the sort method. - * @async - * @param {IAddress[]} [inputs] - * @param {IAddress[]} [outputs] - * @param {number} [satsPerByte] - * @param {sortMethod} - * @return {Promise} - */ + export interface ICoinSelectResponse { fee: number; inputs: IUtxo[]; outputs: IOutput[]; } +// TODO: Migrate to Beignet export const autoCoinSelect = async ({ inputs = [], outputs = [], @@ -805,7 +414,7 @@ export const autoCoinSelect = async ({ let addressTypes = { inputs: {}, outputs: {}, - } as IAddressTypes; + } as IAddressTypesIO; await Promise.all([ newInputs.map(({ address }) => { @@ -1196,11 +805,10 @@ export const updateSendAmount = ({ /** * Updates the OP_RETURN message. * CURRENTLY UNUSED + * // TODO: Migrate to Beignet * @param {string} message * @param {ISendTransaction} [transaction] * @param {number} [index] - * @param {TWalletName} [selectedWallet] - * @param {EAvailableNetwork} [selectedNetwork] */ export const updateMessage = async ({ message, @@ -1462,6 +1070,7 @@ export const setupRbf = async ({ * @param {TWalletName} [selectedWallet] * @param {EAvailableNetwork} [selectedNetwork] * @param {string} oldTxId + * @param {number} oldFee */ export const broadcastBoost = async ({ selectedWallet = getSelectedWallet(), @@ -1527,13 +1136,6 @@ export const broadcastBoost = async ({ } }; -export interface IGetFeeEstimatesResponse { - fastestFee: number; - halfHourFee: number; - hourFee: number; - minimumFee: number; -} - /** * Returns the current fee estimates for the provided network. * @param {EAvailableNetwork} [selectedNetwork] @@ -1582,6 +1184,7 @@ export const getSelectedFeeId = (): EFeeId => { /** * Returns the amount of sats to send to a given output address in the transaction object by its index. + * // TODO: Migrate to Beignet * @param outputIndex * @returns {Result} */