diff --git a/libwallet/assets/btc/account_mixer.go b/libwallet/assets/btc/account_mixer.go new file mode 100644 index 000000000..2bd169071 --- /dev/null +++ b/libwallet/assets/btc/account_mixer.go @@ -0,0 +1,94 @@ +package btc + +import sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" + +const ( + smalletSplitPoint = 000.00262144 + MixedAccountBranch = 0 +) + +func (asset *Asset) AddAccountMixerNotificationListener(accountMixerNotificationListener *AccountMixerNotificationListener, uniqueIdentifier string) error { + return nil +} + +func (asset *Asset) RemoveAccountMixerNotificationListener(uniqueIdentifier string) { +} + +// CreateMixerAccounts creates the two accounts needed for the account mixer. This function +// is added to ease unlocking the wallet before creating accounts. This function should be +// used with auto cspp mixer setup. +func (asset *Asset) CreateMixerAccounts(mixedAccount, unmixedAccount, privPass string) error { + return nil +} + +// SetAccountMixerConfig sets the config for mixed and unmixed account. Private passphrase is verifed +// for security even if not used. This function should be used with manual cspp mixer setup. +func (asset *Asset) SetAccountMixerConfig(mixedAccount, unmixedAccount int32, privPass string) error { + return nil +} + +func (asset *Asset) AccountMixerMixChange() bool { + return false +} + +func (asset *Asset) AccountMixerConfigIsSet() bool { + return false +} + +func (asset *Asset) MixedAccountNumber() int32 { + return -1 +} + +func (asset *Asset) UnmixedAccountNumber() int32 { + return asset.ReadInt32ConfigValueForKey(sharedW.AccountMixerUnmixedAccount, -1) +} + +func (asset *Asset) ClearMixerConfig() { +} + +func (asset *Asset) ReadyToMix(_ int) (bool, error) { + return false, nil +} + +// StartAccountMixer starts the automatic account mixer +func (asset *Asset) StartAccountMixer(walletPassphrase string) error { + return nil +} + +func (asset *Asset) readCSPPConfig() *CSPPConfig { + mixedAccount := asset.MixedAccountNumber() + unmixedAccount := asset.UnmixedAccountNumber() + + if mixedAccount == -1 || unmixedAccount == -1 { + // not configured for mixing + return nil + } + + return &CSPPConfig{ + Mixing: true, + MixedAccount: uint32(mixedAccount), + MixedAccountBranch: uint32(MixedAccountBranch), + ChangeAccount: uint32(unmixedAccount), + TicketSplitAccount: uint32(mixedAccount), // upstream desc: Account to derive fresh addresses from for mixed ticket splits; uses mixedaccount if unset + } +} + +// StopAccountMixer stops the active account mixer +func (asset *Asset) StopAccountMixer() error { + return nil +} + +func (asset *Asset) accountHasMixableOutput(accountNumber int32) bool { + return false +} + +// IsAccountMixerActive returns true if account mixer is active +func (asset *Asset) IsAccountMixerActive() bool { + return false +} + +func (asset *Asset) publishAccountMixerStarted(walletID int) { +} + +func (asset *Asset) publishAccountMixerEnded(walletID int) { +} diff --git a/libwallet/assets/btc/types.go b/libwallet/assets/btc/types.go index efc7782d3..8890e9f52 100644 --- a/libwallet/assets/btc/types.go +++ b/libwallet/assets/btc/types.go @@ -7,6 +7,8 @@ import ( "github.com/lightninglabs/neutrino" ) + + // Amount implements the Asset amount interface for the BTC asset type Amount btcutil.Amount @@ -39,3 +41,19 @@ type ExtraNeutrinoChainService interface { ConnectedCount() int32 Peers() []*neutrino.ServerPeer } + +type CSPPConfig struct { + // Mixing option activates the new version of the coins mixer which is a + // replacement of the old client-server mechanism. Now peer to peer + // mechanism is in place. Ref: https://github.com/decred/dcrwallet/pull/2351 + Mixing bool + MixedAccount uint32 + MixedAccountBranch uint32 + TicketSplitAccount uint32 + ChangeAccount uint32 +} + +type AccountMixerNotificationListener struct { + OnAccountMixerStarted func(walletID int) + OnAccountMixerEnded func(walletID int) +} \ No newline at end of file diff --git a/libwallet/assets/ltc/account_mixer.go b/libwallet/assets/ltc/account_mixer.go new file mode 100644 index 000000000..0afbf1879 --- /dev/null +++ b/libwallet/assets/ltc/account_mixer.go @@ -0,0 +1,94 @@ +package ltc + +import sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" + +const ( + smalletSplitPoint = 000.00262144 + MixedAccountBranch = 0 +) + +func (asset *Asset) AddAccountMixerNotificationListener(accountMixerNotificationListener *AccountMixerNotificationListener, uniqueIdentifier string) error { + return nil +} + +func (asset *Asset) RemoveAccountMixerNotificationListener(uniqueIdentifier string) { +} + +// CreateMixerAccounts creates the two accounts needed for the account mixer. This function +// is added to ease unlocking the wallet before creating accounts. This function should be +// used with auto cspp mixer setup. +func (asset *Asset) CreateMixerAccounts(mixedAccount, unmixedAccount, privPass string) error { + return nil +} + +// SetAccountMixerConfig sets the config for mixed and unmixed account. Private passphrase is verifed +// for security even if not used. This function should be used with manual cspp mixer setup. +func (asset *Asset) SetAccountMixerConfig(mixedAccount, unmixedAccount int32, privPass string) error { + return nil +} + +func (asset *Asset) AccountMixerMixChange() bool { + return false +} + +func (asset *Asset) AccountMixerConfigIsSet() bool { + return false +} + +func (asset *Asset) MixedAccountNumber() int32 { + return -1 +} + +func (asset *Asset) UnmixedAccountNumber() int32 { + return asset.ReadInt32ConfigValueForKey(sharedW.AccountMixerUnmixedAccount, -1) +} + +func (asset *Asset) ClearMixerConfig() { +} + +func (asset *Asset) ReadyToMix(_ int) (bool, error) { + return false, nil +} + +// StartAccountMixer starts the automatic account mixer +func (asset *Asset) StartAccountMixer(walletPassphrase string) error { + return nil +} + +func (asset *Asset) readCSPPConfig() *CSPPConfig { + mixedAccount := asset.MixedAccountNumber() + unmixedAccount := asset.UnmixedAccountNumber() + + if mixedAccount == -1 || unmixedAccount == -1 { + // not configured for mixing + return nil + } + + return &CSPPConfig{ + Mixing: true, + MixedAccount: uint32(mixedAccount), + MixedAccountBranch: uint32(MixedAccountBranch), + ChangeAccount: uint32(unmixedAccount), + TicketSplitAccount: uint32(mixedAccount), // upstream desc: Account to derive fresh addresses from for mixed ticket splits; uses mixedaccount if unset + } +} + +// StopAccountMixer stops the active account mixer +func (asset *Asset) StopAccountMixer() error { + return nil +} + +func (asset *Asset) accountHasMixableOutput(accountNumber int32) bool { + return false +} + +// IsAccountMixerActive returns true if account mixer is active +func (asset *Asset) IsAccountMixerActive() bool { + return false +} + +func (asset *Asset) publishAccountMixerStarted(walletID int) { +} + +func (asset *Asset) publishAccountMixerEnded(walletID int) { +} diff --git a/libwallet/assets/ltc/types.go b/libwallet/assets/ltc/types.go index 667c52330..556307e7e 100644 --- a/libwallet/assets/ltc/types.go +++ b/libwallet/assets/ltc/types.go @@ -27,3 +27,19 @@ func (a Amount) MulF64(f float64) sharedW.AssetAmount { func (a Amount) ToInt() int64 { return int64(ltcutil.Amount(a)) } + +type CSPPConfig struct { + // Mixing option activates the new version of the coins mixer which is a + // replacement of the old client-server mechanism. Now peer to peer + // mechanism is in place. Ref: https://github.com/decred/dcrwallet/pull/2351 + Mixing bool + MixedAccount uint32 + MixedAccountBranch uint32 + TicketSplitAccount uint32 + ChangeAccount uint32 +} + +type AccountMixerNotificationListener struct { + OnAccountMixerStarted func(walletID int) + OnAccountMixerEnded func(walletID int) +} \ No newline at end of file diff --git a/libwallet/assets/wallet/asset_interface.go b/libwallet/assets/wallet/asset_interface.go index 1c28792e6..9a4e9408f 100644 --- a/libwallet/assets/wallet/asset_interface.go +++ b/libwallet/assets/wallet/asset_interface.go @@ -118,4 +118,14 @@ type Asset interface { RemoveSendDestination(id int) SendDestination(id int) *TransactionDestination UpdateSendDestination(id int, address string, atomAmount int64, sendMax bool) error + + CreateMixerAccounts(mixedAccount, unmixedAccount, privPass string) error + IsAccountMixerActive() bool + UnmixedAccountNumber() int32 + MixedAccountNumber() int32 + StartAccountMixer(walletPassphrase string) error + StopAccountMixer() error + // AddAccountMixerNotificationListener(accountMixerNotificationListener *AccountMixerNotificationListener, uniqueIdentifier string) error + RemoveAccountMixerNotificationListener(uniqueIdentifier string) + // SetAccountMixerConfig(mixedAccount, unmixedAccount int32, privPass string) error } diff --git a/libwallet/assets/wallet/types.go b/libwallet/assets/wallet/types.go index 62d18b3b8..2fab670bf 100644 --- a/libwallet/assets/wallet/types.go +++ b/libwallet/assets/wallet/types.go @@ -445,3 +445,8 @@ func (s WordSeedType) AllWords() []string { return []string{} } } + +type AccountMixerNotificationListener struct { + OnAccountMixerStarted func(walletID int) + OnAccountMixerEnded func(walletID int) +} \ No newline at end of file diff --git a/ui/page/privacy/account_mixer_page.go b/ui/page/privacy/account_mixer_page.go index 9ef6c0818..b57b371a7 100644 --- a/ui/page/privacy/account_mixer_page.go +++ b/ui/page/privacy/account_mixer_page.go @@ -5,6 +5,7 @@ import ( "github.com/decred/dcrd/dcrutil/v4" "github.com/crypto-power/cryptopower/app" + "github.com/crypto-power/cryptopower/libwallet/assets/btc" "github.com/crypto-power/cryptopower/libwallet/assets/dcr" sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" "github.com/crypto-power/cryptopower/ui/cryptomaterial" @@ -27,7 +28,7 @@ type AccountMixerPage struct { *app.GenericPageModal pageContainer layout.List - dcrWallet *dcr.Asset + wallet sharedW.Asset settingsCollapsible *cryptomaterial.Collapsible unmixedAccount *cryptomaterial.Clickable @@ -44,11 +45,11 @@ type AccountMixerPage struct { mixerCompleted bool } -func NewAccountMixerPage(l *load.Load, wallet *dcr.Asset) *AccountMixerPage { +func NewAccountMixerPage(l *load.Load, wallet sharedW.Asset) *AccountMixerPage { return &AccountMixerPage{ Load: l, GenericPageModal: app.NewGenericPageModal(AccountMixerPageID), - dcrWallet: wallet, + wallet: wallet, toggleMixer: l.Theme.Switch(), mixerProgress: l.Theme.ProgressBar(0), settingsCollapsible: l.Theme.Collapsible(), @@ -63,22 +64,22 @@ func NewAccountMixerPage(l *load.Load, wallet *dcr.Asset) *AccountMixerPage { // the page is displayed. // Part of the load.Page interface. func (pg *AccountMixerPage) OnNavigatedTo() { - if pg.dcrWallet.IsSynced() { + if pg.wallet.IsSynced() { // Listen for notifications only when the wallet is fully synced. pg.listenForMixerNotifications() // listener is stopped in OnNavigatedFrom(). } - pg.toggleMixer.SetChecked(pg.dcrWallet.IsAccountMixerActive()) + pg.toggleMixer.SetChecked(pg.wallet.IsAccountMixerActive()) pg.mixerProgress.Height = values.MarginPadding18 pg.mixerProgress.Radius = cryptomaterial.Radius(2) - totalBalance, _ := components.CalculateTotalWalletsBalance(pg.dcrWallet) // TODO - handle error + totalBalance, _ := components.CalculateTotalWalletsBalance(pg.wallet) // TODO - handle error pg.totalWalletBalance = totalBalance.Total // get balance information pg.getMixerBalance() } func (pg *AccountMixerPage) getMixerBalance() { - accounts, err := pg.dcrWallet.GetAccountsRaw() + accounts, err := pg.wallet.GetAccountsRaw() if err != nil { log.Error("could not load mixer account information. Please try again.") } @@ -90,9 +91,9 @@ func (pg *AccountMixerPage) getMixerBalance() { vm = append(vm, preference.ItemPreference{Key: acct.Name, Value: acct.Name}) } - if acct.Number == pg.dcrWallet.MixedAccountNumber() { + if acct.Number == pg.wallet.MixedAccountNumber() { pg.mixedBalance = acct.Balance.Total - } else if acct.Number == pg.dcrWallet.UnmixedAccountNumber() { + } else if acct.Number == pg.wallet.UnmixedAccountNumber() { pg.unmixedBalance = acct.Balance.Total } } @@ -201,7 +202,7 @@ func (pg *AccountMixerPage) mixerHeaderContent() layout.FlexChild { }.Layout(gtx, pg.Theme.Separator().Layout) }), layout.Rigid(func(gtx C) D { - if !pg.dcrWallet.IsAccountMixerActive() { + if !pg.wallet.IsAccountMixerActive() { return layout.Inset{Top: values.MarginPadding16}.Layout(gtx, func(_ C) D { return D{} }) @@ -267,7 +268,7 @@ func (pg *AccountMixerPage) mixerSettings(l *load.Load) layout.FlexChild { }) }), layout.Rigid(func(gtx C) D { - if pg.dcrWallet.IsAccountMixerActive() { + if pg.wallet.IsAccountMixerActive() { return D{} } return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { @@ -339,7 +340,7 @@ func (pg *AccountMixerPage) HandleUserInteractions(gtx C) { SetPositiveButtonText(values.String(values.StrYes)). SetPositiveButtonCallback(func(_ bool, _ *modal.InfoModal) bool { pg.toggleMixer.SetChecked(false) - go func() { _ = pg.dcrWallet.StopAccountMixer() }() + go func() { _ = pg.wallet.StopAccountMixer() }() return true }) pg.ParentWindow().ShowModal(info) @@ -354,7 +355,7 @@ func (pg *AccountMixerPage) HandleUserInteractions(gtx C) { // get account number for the selected wallet name acctNum := func(val string) int32 { - num, err := pg.dcrWallet.AccountNumber(val) + num, err := pg.wallet.AccountNumber(val) if err != nil { log.Error(err.Error()) return -1 @@ -363,7 +364,7 @@ func (pg *AccountMixerPage) HandleUserInteractions(gtx C) { } if pg.mixedAccount.Clicked(gtx) { - name, err := pg.dcrWallet.AccountName(pg.dcrWallet.MixedAccountNumber()) + name, err := pg.wallet.AccountName(pg.wallet.MixedAccountNumber()) if err != nil { log.Error(err.Error()) } @@ -382,7 +383,7 @@ func (pg *AccountMixerPage) HandleUserInteractions(gtx C) { IsWallet(true). UpdateValues(func(val string) { if acctNum(val) != -1 { - pg.dcrWallet.SetInt32ConfigValueForKey(sharedW.AccountMixerMixedAccount, acctNum(val)) + pg.wallet.SetInt32ConfigValueForKey(sharedW.AccountMixerMixedAccount, acctNum(val)) pg.getMixerBalance() } }) @@ -390,7 +391,7 @@ func (pg *AccountMixerPage) HandleUserInteractions(gtx C) { } if pg.unmixedAccount.Clicked(gtx) { - name, err := pg.dcrWallet.AccountName(pg.dcrWallet.UnmixedAccountNumber()) + name, err := pg.wallet.AccountName(pg.wallet.UnmixedAccountNumber()) if err != nil { log.Error(err.Error()) } @@ -410,7 +411,7 @@ func (pg *AccountMixerPage) HandleUserInteractions(gtx C) { IsWallet(true). UpdateValues(func(val string) { if acctNum(val) != -1 { - pg.dcrWallet.SetInt32ConfigValueForKey(sharedW.AccountMixerUnmixedAccount, acctNum(val)) + pg.wallet.SetInt32ConfigValueForKey(sharedW.AccountMixerUnmixedAccount, acctNum(val)) pg.getMixerBalance() } }) @@ -419,12 +420,12 @@ func (pg *AccountMixerPage) HandleUserInteractions(gtx C) { } func (pg *AccountMixerPage) getMixerAccounts(isFilterMixed bool) []preference.ItemPreference { - filterAccountNumber := pg.dcrWallet.UnmixedAccountNumber() + filterAccountNumber := pg.wallet.UnmixedAccountNumber() if isFilterMixed { - filterAccountNumber = pg.dcrWallet.MixedAccountNumber() + filterAccountNumber = pg.wallet.MixedAccountNumber() } - accountFilter, err := pg.dcrWallet.AccountName(filterAccountNumber) + accountFilter, err := pg.wallet.AccountName(filterAccountNumber) if err != nil { log.Error(err.Error()) } @@ -446,7 +447,7 @@ func (pg *AccountMixerPage) showModalPasswordStartAccountMixer() { }). PositiveButton(values.String(values.StrConfirm), func(password string, pm *modal.PasswordModal) bool { go func() { - err := pg.dcrWallet.StartAccountMixer(password) + err := pg.wallet.StartAccountMixer(password) if err != nil { pg.Toast.NotifyError(err.Error()) pg.toggleMixer.SetChecked(false) @@ -460,23 +461,51 @@ func (pg *AccountMixerPage) showModalPasswordStartAccountMixer() { } func (pg *AccountMixerPage) listenForMixerNotifications() { - accountMixerNotificationListener := &dcr.AccountMixerNotificationListener{ - OnAccountMixerStarted: func(_ int) { - pg.Toast.Notify(values.String(values.StrMixerStart)) - pg.getMixerBalance() - pg.ParentWindow().Reload() - }, - OnAccountMixerEnded: func(_ int) { - pg.mixerCompleted = true - pg.getMixerBalance() - pg.ParentWindow().Reload() - }, - } - err := pg.dcrWallet.AddAccountMixerNotificationListener(accountMixerNotificationListener, AccountMixerPageID) - if err != nil { - log.Errorf("Error adding account mixer notification listener: %+v", err) - return + switch w := pg.wallet.(type) { + case *dcr.Asset: + accountMixerNotificationListener := &dcr.AccountMixerNotificationListener{ + OnAccountMixerStarted: func(_ int) { + pg.Toast.Notify(values.String(values.StrMixerStart)) + pg.getMixerBalance() + pg.ParentWindow().Reload() + }, + OnAccountMixerEnded: func(_ int) { + pg.mixerCompleted = true + pg.getMixerBalance() + pg.ParentWindow().Reload() + }, + } + err := w.AddAccountMixerNotificationListener(accountMixerNotificationListener, AccountMixerPageID) + if err != nil { + log.Errorf("Error adding account mixer notification listener: %+v", err) + return + } + + case *btc.Asset: + accountMixerNotificationListener := &btc.AccountMixerNotificationListener{ + OnAccountMixerStarted: func(_ int) { + pg.Toast.Notify(values.String(values.StrMixerStart)) + pg.getMixerBalance() + pg.ParentWindow().Reload() + }, + OnAccountMixerEnded: func(_ int) { + pg.mixerCompleted = true + pg.getMixerBalance() + pg.ParentWindow().Reload() + }, + } + + err := w.AddAccountMixerNotificationListener(accountMixerNotificationListener, AccountMixerPageID) + if err != nil { + log.Errorf("Error adding account mixer notification listener: %+v", err) + return + } } + // err := pg.wallet.AddAccountMixerNotificationListener(accountMixerNotificationListener, AccountMixerPageID) + // if err != nil { + // log.Errorf("Error adding account mixer notification listener: %+v", err) + // return + // } // this is needed to refresh the UI on every block txAndBlockNotificationListener := &sharedW.TxAndBlockNotificationListener{ @@ -485,7 +514,7 @@ func (pg *AccountMixerPage) listenForMixerNotifications() { pg.ParentWindow().Reload() }, } - err = pg.dcrWallet.AddTxAndBlockNotificationListener(txAndBlockNotificationListener, AccountMixerPageID) + err := pg.wallet.AddTxAndBlockNotificationListener(txAndBlockNotificationListener, AccountMixerPageID) if err != nil { log.Errorf("Error adding tx and block notification listener: %v", err) return @@ -493,8 +522,8 @@ func (pg *AccountMixerPage) listenForMixerNotifications() { } func (pg *AccountMixerPage) stopMixerNtfnListeners() { - pg.dcrWallet.RemoveTxAndBlockNotificationListener(AccountMixerPageID) - pg.dcrWallet.RemoveAccountMixerNotificationListener(AccountMixerPageID) + pg.wallet.RemoveTxAndBlockNotificationListener(AccountMixerPageID) + pg.wallet.RemoveAccountMixerNotificationListener(AccountMixerPageID) } // OnNavigatedFrom is called when the page is about to be removed from diff --git a/ui/page/privacy/manual_mixer_setup_page.go b/ui/page/privacy/manual_mixer_setup_page.go index 6deaa7ca0..3066a3544 100644 --- a/ui/page/privacy/manual_mixer_setup_page.go +++ b/ui/page/privacy/manual_mixer_setup_page.go @@ -5,8 +5,10 @@ import ( "gioui.org/widget" "github.com/crypto-power/cryptopower/app" + "github.com/crypto-power/cryptopower/libwallet/assets/btc" "github.com/crypto-power/cryptopower/libwallet/assets/dcr" sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" + libutils "github.com/crypto-power/cryptopower/libwallet/utils" "github.com/crypto-power/cryptopower/ui/cryptomaterial" "github.com/crypto-power/cryptopower/ui/load" "github.com/crypto-power/cryptopower/ui/modal" @@ -36,15 +38,15 @@ type ManualMixerSetupPage struct { pageContainer *widget.List - dcrWallet *dcr.Asset + wallet sharedW.Asset } -func NewManualMixerSetupPage(l *load.Load, dcrWallet *dcr.Asset) *ManualMixerSetupPage { +func NewManualMixerSetupPage(l *load.Load, wallet sharedW.Asset) *ManualMixerSetupPage { pg := &ManualMixerSetupPage{ Load: l, GenericPageModal: app.NewGenericPageModal(ManualMixerSetupPageID), toPrivacySetup: l.Theme.Button(values.String(values.StrSetUp)), - dcrWallet: dcrWallet, + wallet: wallet, pageContainer: &widget.List{ List: layout.List{Axis: layout.Vertical}, }, @@ -77,7 +79,7 @@ func NewManualMixerSetupPage(l *load.Load, dcrWallet *dcr.Asset) *ManualMixerSet return true }). - Setup(dcrWallet) + Setup(wallet) // Unmixed account picker pg.unmixedAccountSelector = components.NewAccountDropdown(l). @@ -90,8 +92,19 @@ func NewManualMixerSetupPage(l *load.Load, dcrWallet *dcr.Asset) *ManualMixerSet mixedAccNo = mixedAcc.Number } + var defaultAccNum int32 = -1 + switch wal.GetAssetType() { + case libutils.DCRWalletAsset: + defaultAccNum = dcr.DefaultAccountNum + case libutils.BTCWalletAsset: + defaultAccNum = btc.DefaultAccountNum + } + // Imported, watch only and default wallet accounts are invalid to use as an unmixed account - accountIsValid := account.Number != load.MaxInt32 && !wal.IsWatchingOnlyWallet() && account.Number != dcr.DefaultAccountNum && !utils.IsImportedAccount(dcrWallet.GetAssetType(), account) + accountIsValid := account.Number != load.MaxInt32 && + !wal.IsWatchingOnlyWallet() && + (defaultAccNum < 0 || account.Number != defaultAccNum) && + !utils.IsImportedAccount(wal.GetAssetType(), account) // Account is invalid if already selected by mixed account selector. if !accountIsValid || account.Number == mixedAccNo { @@ -100,7 +113,7 @@ func NewManualMixerSetupPage(l *load.Load, dcrWallet *dcr.Asset) *ManualMixerSet return true }). - Setup(dcrWallet) + Setup(wallet) _, pg.infoButton = components.SubpageHeaderButtons(l) pg.backButton = components.GetBackButton(l) @@ -113,8 +126,8 @@ func NewManualMixerSetupPage(l *load.Load, dcrWallet *dcr.Asset) *ManualMixerSet // the page is displayed. // Part of the load.Page interface. func (pg *ManualMixerSetupPage) OnNavigatedTo() { - _ = pg.mixedAccountSelector.Setup(pg.dcrWallet) - _ = pg.unmixedAccountSelector.Setup(pg.dcrWallet) + _ = pg.mixedAccountSelector.Setup(pg.wallet) + _ = pg.unmixedAccountSelector.Setup(pg.wallet) } // Layout draws the page UI components into the provided layout context @@ -269,26 +282,36 @@ func (pg *ManualMixerSetupPage) showModalSetupMixerAcct() { } mixedAcctNumber := pg.mixedAccountSelector.SelectedAccount().Number unmixedAcctNumber := pg.unmixedAccountSelector.SelectedAccount().Number - err := pg.dcrWallet.SetAccountMixerConfig(mixedAcctNumber, unmixedAcctNumber, password) - if err != nil { - return errfunc(err) + + switch w := pg.wallet.(type) { + case *dcr.Asset: + err := w.SetAccountMixerConfig(mixedAcctNumber, unmixedAcctNumber, password) + if err != nil { + return errfunc(err) + } + + case *btc.Asset: + err := w.SetAccountMixerConfig(mixedAcctNumber, unmixedAcctNumber, password) + if err != nil { + return errfunc(err) + } } // rename mixed account - err = pg.dcrWallet.RenameAccount(mixedAcctNumber, values.String(values.StrMixed)) + err := pg.wallet.RenameAccount(mixedAcctNumber, values.String(values.StrMixed)) if err != nil { return errfunc(err) } // rename unmixed account - err = pg.dcrWallet.RenameAccount(unmixedAcctNumber, values.String(values.StrUnmixed)) + err = pg.wallet.RenameAccount(unmixedAcctNumber, values.String(values.StrUnmixed)) if err != nil { return errfunc(err) } pm.Dismiss() - pg.ParentNavigator().Display(NewAccountMixerPage(pg.Load, pg.dcrWallet)) + pg.ParentNavigator().Display(NewAccountMixerPage(pg.Load, pg.wallet)) return true }) diff --git a/ui/page/privacy/setup_mixer_accounts_page.go b/ui/page/privacy/setup_mixer_accounts_page.go index adcbd0760..78a7b2d4f 100644 --- a/ui/page/privacy/setup_mixer_accounts_page.go +++ b/ui/page/privacy/setup_mixer_accounts_page.go @@ -6,7 +6,7 @@ import ( "gioui.org/widget" "github.com/crypto-power/cryptopower/app" - "github.com/crypto-power/cryptopower/libwallet/assets/dcr" + sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" "github.com/crypto-power/cryptopower/ui/cryptomaterial" "github.com/crypto-power/cryptopower/ui/load" "github.com/crypto-power/cryptopower/ui/modal" @@ -23,7 +23,7 @@ type SetupMixerAccountsPage struct { // helper methods for accessing the PageNavigator that displayed this page // and the root WindowNavigator. *app.GenericPageModal - dcrWallet *dcr.Asset + wallet sharedW.Asset backButton cryptomaterial.IconButton infoButton cryptomaterial.IconButton @@ -34,11 +34,11 @@ type SetupMixerAccountsPage struct { manualEnabled bool } -func NewSetupMixerAccountsPage(l *load.Load, dcrWallet *dcr.Asset) *SetupMixerAccountsPage { +func NewSetupMixerAccountsPage(l *load.Load, wallet sharedW.Asset) *SetupMixerAccountsPage { pg := &SetupMixerAccountsPage{ Load: l, GenericPageModal: app.NewGenericPageModal(SetupMixerAccountsPageID), - dcrWallet: dcrWallet, + wallet: wallet, } pg.nextIcon = cryptomaterial.NewIcon(pg.Theme.Icons.NavigationArrowForward) pg.nextIcon.Color = pg.Theme.Color.Gray1 @@ -54,7 +54,7 @@ func NewSetupMixerAccountsPage(l *load.Load, dcrWallet *dcr.Asset) *SetupMixerAc // the page is displayed. // Part of the load.Page interface. func (pg *SetupMixerAccountsPage) OnNavigatedTo() { - accts, err := pg.dcrWallet.GetAccountsRaw() + accts, err := pg.wallet.GetAccountsRaw() if err != nil { log.Errorf("Unable to get accounts to set up mixer: %v", err) return @@ -192,7 +192,7 @@ func (pg *SetupMixerAccountsPage) HandleUserInteractions(gtx C) { window: pg.ParentWindow(), pageNavigator: pg.ParentNavigator(), checkBox: pg.Theme.CheckBox(new(widget.Bool), values.String(values.StrMoveFundsFrmDefaultToUnmixed)), - }, pg.dcrWallet) + }, pg.wallet) } if pg.manualSetupClickable.Clicked(gtx) { @@ -201,7 +201,7 @@ func (pg *SetupMixerAccountsPage) HandleUserInteractions(gtx C) { info := modal.NewErrorModal(pg.Load, notEnoughAccounts, modal.DefaultClickFunc()) pg.ParentWindow().ShowModal(info) } else { - pg.ParentNavigator().Display(NewManualMixerSetupPage(pg.Load, pg.dcrWallet)) + pg.ParentNavigator().Display(NewManualMixerSetupPage(pg.Load, pg.wallet)) } } } diff --git a/ui/page/privacy/setup_privacy_page.go b/ui/page/privacy/setup_privacy_page.go index 6b7292065..f12e2bb85 100644 --- a/ui/page/privacy/setup_privacy_page.go +++ b/ui/page/privacy/setup_privacy_page.go @@ -8,7 +8,7 @@ import ( "gioui.org/unit" "github.com/crypto-power/cryptopower/app" - "github.com/crypto-power/cryptopower/libwallet/assets/dcr" + sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" "github.com/crypto-power/cryptopower/ui/cryptomaterial" "github.com/crypto-power/cryptopower/ui/load" "github.com/crypto-power/cryptopower/ui/page/components" @@ -29,7 +29,7 @@ type SetupPrivacyPage struct { // helper methods for accessing the PageNavigator that displayed this page // and the root WindowNavigator. *app.GenericPageModal - wallet *dcr.Asset + wallet sharedW.Asset toPrivacySetup cryptomaterial.Button @@ -37,7 +37,7 @@ type SetupPrivacyPage struct { infoButton cryptomaterial.IconButton } -func NewSetupPrivacyPage(l *load.Load, wallet *dcr.Asset) *SetupPrivacyPage { +func NewSetupPrivacyPage(l *load.Load, wallet sharedW.Asset) *SetupPrivacyPage { pg := &SetupPrivacyPage{ Load: l, GenericPageModal: app.NewGenericPageModal(SetupPrivacyPageID), @@ -62,8 +62,9 @@ func (pg *SetupPrivacyPage) OnNavigatedTo() {} // Part of the load.Page interface. func (pg *SetupPrivacyPage) Layout(gtx C) D { stakeShuffleDesc := fmt.Sprintf("%s\n%s", - values.String(values.StrSetUpStakeShuffleIntroDesc), - values.String(values.StrSetUpStakeShuffleIntroSubDesc)) + values.StringF(values.StrSetUpStakeShuffleIntroDesc, pg.wallet.GetAssetType().String()), + values.StringF(values.StrSetUpStakeShuffleIntroSubDesc, pg.wallet.GetAssetType().String()), + ) inset := layout.Inset{ Top: values.MarginPadding24, diff --git a/ui/page/privacy/shared_modals.go b/ui/page/privacy/shared_modals.go index 8c5c49a5e..e585d57f0 100644 --- a/ui/page/privacy/shared_modals.go +++ b/ui/page/privacy/shared_modals.go @@ -2,7 +2,7 @@ package privacy import ( "github.com/crypto-power/cryptopower/app" - "github.com/crypto-power/cryptopower/libwallet/assets/dcr" + sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" "github.com/crypto-power/cryptopower/libwallet/utils" "github.com/crypto-power/cryptopower/ui/cryptomaterial" "github.com/crypto-power/cryptopower/ui/load" @@ -28,7 +28,7 @@ func showInfoModal(conf *sharedModalConfig, title, body, btnText string, isError conf.window.ShowModal(info) } -func showModalSetupMixerInfo(conf *sharedModalConfig, dcrWallet *dcr.Asset) { +func showModalSetupMixerInfo(conf *sharedModalConfig, wallet sharedW.Asset) { info := modal.NewCustomModal(conf.Load). Title(values.String(values.StrMultipleMixerAccNeeded)). SetupWithTemplate(modal.SetupMixerInfoTemplate). @@ -36,20 +36,20 @@ func showModalSetupMixerInfo(conf *sharedModalConfig, dcrWallet *dcr.Asset) { SetNegativeButtonText(values.String(values.StrCancel)). SetPositiveButtonText(values.String(values.StrInitiateSetup)). SetPositiveButtonCallback(func(movefundsChecked bool, _ *modal.InfoModal) bool { - showModalSetupMixerAcct(conf, dcrWallet, movefundsChecked) + showModalSetupMixerAcct(conf, wallet, movefundsChecked) return true }) conf.window.ShowModal(info) } -func showModalSetupMixerAcct(conf *sharedModalConfig, dcrWallet *dcr.Asset, movefundsChecked bool) { - if dcrWallet.GetAssetType() != utils.DCRWalletAsset { +func showModalSetupMixerAcct(conf *sharedModalConfig, wallet sharedW.Asset, movefundsChecked bool) { + if wallet.GetAssetType() != utils.DCRWalletAsset || wallet.GetAssetType() != utils.BTCWalletAsset { log.Warnf("Mixer Account for (%v) not supported.", - dcrWallet.GetAssetType()) + wallet.GetAssetType()) return } - accounts, _ := dcrWallet.GetAccountsRaw() + accounts, _ := wallet.GetAccountsRaw() for _, acct := range accounts.Accounts { if acct.Name == values.String(values.StrMixed) || acct.Name == values.String(values.StrUnmixed) { info := modal.NewErrorModal(conf.Load, values.String(values.StrTakenAccount), modal.DefaultClickFunc()). @@ -70,14 +70,14 @@ func showModalSetupMixerAcct(conf *sharedModalConfig, dcrWallet *dcr.Asset, move Title(values.String(values.StrConfirmToCreateAccs)). SetPositiveButtonCallback(func(_, password string, pm *modal.CreatePasswordModal) bool { defer pm.Dismiss() - err := dcrWallet.CreateMixerAccounts(values.String(values.StrMixed), values.String(values.StrUnmixed), password) + err := wallet.CreateMixerAccounts(values.String(values.StrMixed), values.String(values.StrUnmixed), password) if err != nil { pm.SetError(err.Error()) return false } if movefundsChecked { - err := moveFundsFromDefaultToUnmixed(conf, dcrWallet, password) + err := moveFundsFromDefaultToUnmixed(conf, wallet, password) if err != nil { log.Error(err) txt := values.StringF(values.StrErrorMovingFunds, err.Error()) @@ -86,7 +86,7 @@ func showModalSetupMixerAcct(conf *sharedModalConfig, dcrWallet *dcr.Asset, move } } - conf.pageNavigator.Display(NewAccountMixerPage(conf.Load, dcrWallet)) + conf.pageNavigator.Display(NewAccountMixerPage(conf.Load, wallet)) return true }) @@ -95,8 +95,8 @@ func showModalSetupMixerAcct(conf *sharedModalConfig, dcrWallet *dcr.Asset, move // moveFundsFromDefaultToUnmixed moves funds from the default wallet account to the // newly created unmixed account -func moveFundsFromDefaultToUnmixed(conf *sharedModalConfig, dcrWallet *dcr.Asset, password string) error { - acc, err := dcrWallet.GetAccountsRaw() +func moveFundsFromDefaultToUnmixed(conf *sharedModalConfig, wallet sharedW.Asset, password string) error { + acc, err := wallet.GetAccountsRaw() if err != nil { return err } @@ -110,33 +110,33 @@ func moveFundsFromDefaultToUnmixed(conf *sharedModalConfig, dcrWallet *dcr.Asset return nil } - destinationAccount := dcrWallet.UnmixedAccountNumber() + destinationAccount := wallet.UnmixedAccountNumber() - destinationAddress, err := dcrWallet.CurrentAddress(destinationAccount) + destinationAddress, err := wallet.CurrentAddress(destinationAccount) if err != nil { return err } - err = dcrWallet.NewUnsignedTx(sourceAccount.Number, nil) + err = wallet.NewUnsignedTx(sourceAccount.Number, nil) if err != nil { return err } // get tx fees - feeAndSize, err := dcrWallet.EstimateFeeAndSize() + feeAndSize, err := wallet.EstimateFeeAndSize() if err != nil { return err } // calculate max amount to be sent amountAtom := sourceAccount.Balance.Spendable.ToInt() - feeAndSize.Fee.UnitValue - err = dcrWallet.AddSendDestination(0, destinationAddress, amountAtom, true) + err = wallet.AddSendDestination(0, destinationAddress, amountAtom, true) if err != nil { return err } // send fund - _, err = dcrWallet.Broadcast(password, "") + _, err = wallet.Broadcast(password, "") if err != nil { return err } diff --git a/ui/page/wallet/single_wallet_main_page.go b/ui/page/wallet/single_wallet_main_page.go index 87dfc5924..e22ad192b 100644 --- a/ui/page/wallet/single_wallet_main_page.go +++ b/ui/page/wallet/single_wallet_main_page.go @@ -13,6 +13,7 @@ import ( "gioui.org/widget/material" "github.com/crypto-power/cryptopower/app" + "github.com/crypto-power/cryptopower/libwallet/assets/btc" "github.com/crypto-power/cryptopower/libwallet/assets/dcr" sharedW "github.com/crypto-power/cryptopower/libwallet/assets/wallet" libutils "github.com/crypto-power/cryptopower/libwallet/utils" @@ -233,29 +234,30 @@ func (swmp *SingleWalletMasterPage) initTabOptions() { commonTabs = append(commonTabs[:1], append(sendTab, commonTabs[1:]...)...) } - // Insert DCR-specific tabs if the wallet's asset type is DCR, - // and adjust the logic to exclude 'StrStakeShuffle' for watching-only wallets. - if swmp.selectedWallet.GetAssetType() == libutils.DCRWalletAsset { - dcrSpecificTabs := []string{} + // Add StrStakeShuffle for DCR & BTC assets and exclude StrStakeShuffle for watching-only wallets. + assetType := swmp.selectedWallet.GetAssetType() + if assetType == libutils.DCRWalletAsset || assetType == libutils.BTCWalletAsset { + specificTabs := []string{} - // Conditionally add 'StrStakeShuffle' if the wallet is not a watch-only wallet. + // Add StakeShuffle for both DCR & BTC assets when not a watch-only wallet if !swmp.selectedWallet.IsWatchingOnlyWallet() { - dcrSpecificTabs = append(dcrSpecificTabs, values.StrStakeShuffle) + specificTabs = append(specificTabs, values.StrStakeShuffle) } - // Always add 'StrStaking' for DCR asset type wallets. - dcrSpecificTabs = append(dcrSpecificTabs, values.StrStaking) + // Add StrStaking only for DCR + if assetType == libutils.DCRWalletAsset { + specificTabs = append(specificTabs, values.StrStaking) + } - // Find the correct insertion index for DCR-specific tabs before 'StrAccounts'. + // Insert before StrAccounts insertIndex := 3 // Default position before 'StrAccounts' in the commonTabs. - // If 'Send' has been added, adjust the insertIndex accordingly. - if !swmp.selectedWallet.IsWatchingOnlyWallet() { + if !swmp.selectedWallet.IsWatchingOnlyWallet() { insertIndex++ } - // Update the commonTabs with DCR-specific items at the determined index. - commonTabs = append(commonTabs[:insertIndex], append(dcrSpecificTabs, commonTabs[insertIndex:]...)...) + // Update the commonTabs with DCR & BTC specific items at the determined index. + commonTabs = append(commonTabs[:insertIndex], append(specificTabs, commonTabs[insertIndex:]...)...) } swmp.PageNavigationTab = swmp.Theme.SegmentedControl(commonTabs, cryptomaterial.SegmentTypeSplit) @@ -419,13 +421,23 @@ func (swmp *SingleWalletMasterPage) navigateToSelectedTab() { txPage.DisableUniformTab() pg = txPage case values.StrStakeShuffle: - dcrW := swmp.selectedWallet.(*dcr.Asset) - if dcrW != nil { - if !dcrW.AccountMixerConfigIsSet() { - pg = privacy.NewSetupPrivacyPage(swmp.Load, dcrW) + switch w := swmp.selectedWallet.(type) { + case *dcr.Asset: + if !w.AccountMixerConfigIsSet() { + pg = privacy.NewSetupPrivacyPage(swmp.Load, w) } else { - pg = privacy.NewAccountMixerPage(swmp.Load, dcrW) + pg = privacy.NewAccountMixerPage(swmp.Load, w) } + + case *btc.Asset: + if !w.AccountMixerConfigIsSet() { + pg = privacy.NewSetupPrivacyPage(swmp.Load, w) + } else { + pg = privacy.NewAccountMixerPage(swmp.Load, w) + } + + default: + // no supported wallet selected } case values.StrStaking: dcrW := swmp.selectedWallet.(*dcr.Asset) diff --git a/ui/values/localizable/en.go b/ui/values/localizable/en.go index d20d37a58..e0cec0e22 100644 --- a/ui/values/localizable/en.go +++ b/ui/values/localizable/en.go @@ -560,8 +560,8 @@ const EN = ` "mixingNotSetUp" = "Set up mixing from the StakeShuffle tab in order to disable." "setUpNeededAccs" = "Set up needed accounts" "setUpStakeShuffleIntro" = "How does StakeShuffle++ mixer enhance your privacy?" -"setUpStakeShuffleIntroDesc" = "The Shuffle++ mixer can mix your DCR through CoinJoin transactions." -"setUpStakeShuffleIntroSubDesc" = "Using mixed DCR protects you from exposing your financial activities to the public (e.g. how much you own, who pays you)." +"setUpStakeShuffleIntroDesc" = "The Shuffle++ mixer can mix your %s through CoinJoin transactions." +"setUpStakeShuffleIntroSubDesc" = "Using mixed %s protects you from exposing your financial activities to the public (e.g. how much you own, who pays you)." "setUpStakeShuffleIntroButton" = "Set up mixer for this wallet" "setUpStakeShuffleAutoOrManualA" = "Mixer Setup" "setUpStakeShuffleAutoOrManualB" = "Two dedicated accounts will be set up to use the mixer:"