github.com/status-im/status-go@v1.1.0/protocol/messenger_wallet.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"time"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	"go.uber.org/zap"
    10  
    11  	ethcommon "github.com/ethereum/go-ethereum/common"
    12  	"github.com/status-im/status-go/account"
    13  	"github.com/status-im/status-go/constants"
    14  	"github.com/status-im/status-go/eth-node/types"
    15  	"github.com/status-im/status-go/multiaccounts/accounts"
    16  	walletsettings "github.com/status-im/status-go/multiaccounts/settings_wallet"
    17  	"github.com/status-im/status-go/protocol/common"
    18  	"github.com/status-im/status-go/protocol/encryption/multidevice"
    19  	"github.com/status-im/status-go/protocol/protobuf"
    20  )
    21  
    22  var (
    23  	checkBalancesInterval = time.Minute * 10
    24  
    25  	ErrCannotChangeKeypairName = errors.New("cannot change profile keypair name")
    26  )
    27  
    28  func (m *Messenger) retrieveWalletBalances() error {
    29  	if m.walletAPI == nil {
    30  		m.logger.Warn("wallet api not enabled")
    31  	}
    32  	accounts, err := m.settings.GetActiveAccounts()
    33  	if err != nil {
    34  		return err
    35  	}
    36  
    37  	if len(accounts) == 0 {
    38  		m.logger.Info("no accounts to sync wallet balance")
    39  	}
    40  
    41  	var ethAccounts []ethcommon.Address
    42  
    43  	for _, acc := range accounts {
    44  		m.logger.Info("syncing wallet address", zap.String("account", acc.Address.Hex()))
    45  		ethAccounts = append(ethAccounts, ethcommon.BytesToAddress(acc.Address.Bytes()))
    46  	}
    47  
    48  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
    49  	defer cancel()
    50  
    51  	// TODO: publish tokens as a signal
    52  	_, err = m.walletAPI.FetchOrGetCachedWalletBalances(ctx, ethAccounts)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	return nil
    58  }
    59  
    60  func (m *Messenger) watchWalletBalances() {
    61  	m.logger.Info("watching wallet balances")
    62  
    63  	if m.walletAPI == nil {
    64  		m.logger.Warn("wallet service not enabled")
    65  		return
    66  	}
    67  	go func() {
    68  		for {
    69  			select {
    70  			case <-time.After(checkBalancesInterval):
    71  
    72  				err := m.retrieveWalletBalances()
    73  				if err != nil {
    74  					m.logger.Error("failed to retrieve wallet balances", zap.Error(err))
    75  				}
    76  			case <-m.quit:
    77  				return
    78  			}
    79  		}
    80  	}()
    81  }
    82  
    83  func (m *Messenger) UpdateKeypairName(keyUID string, name string) error {
    84  	if keyUID == m.account.KeyUID && name != m.account.Name {
    85  		// profile keypair name must always follow profile display name
    86  		return ErrCannotChangeKeypairName
    87  	}
    88  	clock, _ := m.getLastClockWithRelatedChat()
    89  	err := m.settings.UpdateKeypairName(keyUID, name, clock, keyUID == m.account.KeyUID)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	return m.resolveAndSyncKeypairOrJustWalletAccount(keyUID, types.Address{}, clock, m.dispatchMessage)
    95  }
    96  
    97  func (m *Messenger) MoveWalletAccount(fromPosition int64, toPosition int64) error {
    98  	clock, _ := m.getLastClockWithRelatedChat()
    99  
   100  	err := m.settings.MoveWalletAccount(fromPosition, toPosition, clock)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	return m.syncAccountsPositions(m.dispatchMessage)
   106  }
   107  
   108  func (m *Messenger) resolveAndSetAccountPropsMaintainedByBackend(acc *accounts.Account) error {
   109  	// Account position is fully maintained by the backend, no need client to set it explicitly.
   110  	// To support DragAndDrop feature for accounts there is exposed `MoveWalletAccount` which
   111  	// moves an account to the passed position.
   112  	//
   113  	// Account operability is fully maintained by the backend, for new accounts created on this device
   114  	// it is always set to fully operable, while for accounts received by syncing process or fetched from waku
   115  	// is set by logic placed in `resolveAccountOperability` function.
   116  	//
   117  	// TODO: making not or partially operable accounts fully operable will be added later, but for sure it will
   118  	// be handled by the backend only, no need client to set it explicitly.
   119  
   120  	dbAccount, err := m.settings.GetAccountByAddress(acc.Address)
   121  	if err != nil && err != accounts.ErrDbAccountNotFound {
   122  		return err
   123  	}
   124  	if dbAccount != nil {
   125  		acc.Position = dbAccount.Position
   126  		acc.Operable = dbAccount.Operable
   127  	} else {
   128  		pos, err := m.settings.GetPositionForNextNewAccount()
   129  		if err != nil {
   130  			return err
   131  		}
   132  		acc.Position = pos
   133  		acc.Operable = accounts.AccountFullyOperable
   134  	}
   135  	return nil
   136  }
   137  
   138  func (m *Messenger) SaveOrUpdateKeypair(keypair *accounts.Keypair) error {
   139  	if keypair.KeyUID == m.account.KeyUID && keypair.Name != m.account.Name {
   140  		// profile keypair name must always follow profile display name
   141  		return ErrCannotChangeKeypairName
   142  	}
   143  	clock, _ := m.getLastClockWithRelatedChat()
   144  	keypair.Clock = clock
   145  
   146  	for _, acc := range keypair.Accounts {
   147  		acc.Clock = clock
   148  		err := m.resolveAndSetAccountPropsMaintainedByBackend(acc)
   149  		if err != nil {
   150  			return err
   151  		}
   152  	}
   153  
   154  	err := m.settings.SaveOrUpdateKeypair(keypair)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	return m.resolveAndSyncKeypairOrJustWalletAccount(keypair.KeyUID, types.Address{}, keypair.Clock, m.dispatchMessage)
   160  }
   161  
   162  func (m *Messenger) SaveOrUpdateAccount(acc *accounts.Account) error {
   163  	clock, _ := m.getLastClockWithRelatedChat()
   164  	acc.Clock = clock
   165  
   166  	err := m.resolveAndSetAccountPropsMaintainedByBackend(acc)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	err = m.settings.SaveOrUpdateAccounts([]*accounts.Account{acc}, true)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	err = m.resolveAndSyncKeypairOrJustWalletAccount(acc.KeyUID, acc.Address, acc.Clock, m.dispatchMessage)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	return m.UpdateProfileShowcaseWalletAccount(acc)
   182  }
   183  
   184  func (m *Messenger) MarkKeypairFullyOperable(keyUID string) error {
   185  	clock, _ := m.getLastClockWithRelatedChat()
   186  
   187  	err := m.settings.MarkKeypairFullyOperable(keyUID, clock, true)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	return m.resolveAndSyncKeypairOrJustWalletAccount(keyUID, types.Address{}, clock, m.dispatchMessage)
   193  }
   194  
   195  func (m *Messenger) deleteKeystoreFileForAddress(address types.Address) error {
   196  	acc, err := m.settings.GetAccountByAddress(address)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	if acc.Operable == accounts.AccountNonOperable || acc.Operable == accounts.AccountPartiallyOperable {
   202  		return nil
   203  	}
   204  
   205  	if acc.Type != accounts.AccountTypeWatch {
   206  		kp, err := m.settings.GetKeypairByKeyUID(acc.KeyUID)
   207  		if err != nil {
   208  			return err
   209  		}
   210  
   211  		if !kp.MigratedToKeycard() {
   212  			err = m.accountsManager.DeleteAccount(address)
   213  			var e *account.ErrCannotLocateKeyFile
   214  			if err != nil && !errors.As(err, &e) {
   215  				return err
   216  			}
   217  
   218  			if acc.Type != accounts.AccountTypeKey {
   219  				lastAcccountOfKeypairWithTheSameKey := len(kp.Accounts) == 1
   220  				if lastAcccountOfKeypairWithTheSameKey {
   221  					err = m.accountsManager.DeleteAccount(types.Address(ethcommon.HexToAddress(kp.DerivedFrom)))
   222  					var e *account.ErrCannotLocateKeyFile
   223  					if err != nil && !errors.As(err, &e) {
   224  						return err
   225  					}
   226  				}
   227  			}
   228  		}
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  func (m *Messenger) deleteKeystoreFilesForKeypair(keypair *accounts.Keypair) (err error) {
   235  	if m.accountsManager == nil || keypair == nil || keypair.MigratedToKeycard() {
   236  		return
   237  	}
   238  
   239  	anyAccountFullyOrPartiallyOperable := false
   240  	for _, acc := range keypair.Accounts {
   241  		if acc.Removed || acc.Operable == accounts.AccountNonOperable {
   242  			continue
   243  		}
   244  		if !anyAccountFullyOrPartiallyOperable {
   245  			anyAccountFullyOrPartiallyOperable = true
   246  		}
   247  		if acc.Operable == accounts.AccountPartiallyOperable {
   248  			continue
   249  		}
   250  		err = m.accountsManager.DeleteAccount(acc.Address)
   251  		var e *account.ErrCannotLocateKeyFile
   252  		if err != nil && !errors.As(err, &e) {
   253  			return err
   254  		}
   255  	}
   256  
   257  	if anyAccountFullyOrPartiallyOperable && keypair.Type != accounts.KeypairTypeKey {
   258  		err = m.accountsManager.DeleteAccount(types.Address(ethcommon.HexToAddress(keypair.DerivedFrom)))
   259  		var e *account.ErrCannotLocateKeyFile
   260  		if err != nil && !errors.As(err, &e) {
   261  			return err
   262  		}
   263  	}
   264  
   265  	return
   266  }
   267  
   268  func (m *Messenger) DeleteAccount(address types.Address) error {
   269  	acc, err := m.settings.GetAccountByAddress(address)
   270  	if err != nil {
   271  		return err
   272  	}
   273  
   274  	if acc.Chat {
   275  		return accounts.ErrCannotRemoveProfileAccount
   276  	}
   277  
   278  	if acc.Wallet {
   279  		return accounts.ErrCannotRemoveDefaultWalletAccount
   280  	}
   281  
   282  	err = m.deleteKeystoreFileForAddress(address)
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	clock, _ := m.getLastClockWithRelatedChat()
   288  
   289  	err = m.settings.RemoveAccount(address, clock)
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	err = m.resolveAndSyncKeypairOrJustWalletAccount(acc.KeyUID, acc.Address, clock, m.dispatchMessage)
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	// In case when user deletes an account, we need to send sync message after an account gets deleted,
   300  	// and then (after that) update the positions of other accoutns. That's needed to handle properly
   301  	// accounts order on the paired devices.
   302  	err = m.settings.ResolveAccountsPositions(clock)
   303  	if err != nil {
   304  		return err
   305  	}
   306  	// Since some keypairs may be received out of expected order, we're aligning that by sending accounts position sync msg.
   307  	err = m.syncAccountsPositions(m.dispatchMessage)
   308  	if err != nil {
   309  		return err
   310  	}
   311  
   312  	return m.DeleteProfileShowcaseWalletAccount(acc)
   313  }
   314  
   315  func (m *Messenger) DeleteKeypair(keyUID string) error {
   316  	kp, err := m.settings.GetKeypairByKeyUID(keyUID)
   317  	if err != nil {
   318  		return err
   319  	}
   320  
   321  	if kp.Type == accounts.KeypairTypeProfile {
   322  		return accounts.ErrCannotRemoveProfileKeypair
   323  	}
   324  
   325  	err = m.deleteKeystoreFilesForKeypair(kp)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	clock, _ := m.getLastClockWithRelatedChat()
   331  
   332  	err = m.settings.RemoveKeypair(keyUID, clock)
   333  	if err != nil {
   334  		return err
   335  	}
   336  
   337  	err = m.resolveAndSyncKeypairOrJustWalletAccount(kp.KeyUID, types.Address{}, clock, m.dispatchMessage)
   338  	if err != nil {
   339  		return err
   340  	}
   341  
   342  	// In case when user deletes entire keypair, we need to send sync message after a keypair gets deleted,
   343  	// and then (after that) update the positions of other accoutns. That's needed to handle properly
   344  	// accounts order on the paired devices.
   345  	err = m.settings.ResolveAccountsPositions(clock)
   346  	if err != nil {
   347  		return err
   348  	}
   349  	// Since some keypairs may be received out of expected order, we're aligning that by sending accounts position sync msg.
   350  	return m.syncAccountsPositions(m.dispatchMessage)
   351  }
   352  
   353  func (m *Messenger) prepareSyncAccountMessage(acc *accounts.Account) *protobuf.SyncAccount {
   354  	return &protobuf.SyncAccount{
   355  		Clock:                 acc.Clock,
   356  		Address:               acc.Address.Bytes(),
   357  		KeyUid:                acc.KeyUID,
   358  		PublicKey:             acc.PublicKey,
   359  		Path:                  acc.Path,
   360  		Name:                  acc.Name,
   361  		ColorId:               string(acc.ColorID),
   362  		Emoji:                 acc.Emoji,
   363  		Wallet:                acc.Wallet,
   364  		Chat:                  acc.Chat,
   365  		Hidden:                acc.Hidden,
   366  		Removed:               acc.Removed,
   367  		Operable:              acc.Operable.String(),
   368  		Position:              acc.Position,
   369  		ProdPreferredChainIDs: acc.ProdPreferredChainIDs,
   370  		TestPreferredChainIDs: acc.TestPreferredChainIDs,
   371  	}
   372  }
   373  
   374  func (m *Messenger) getMyInstallationMetadata() (*multidevice.InstallationMetadata, error) {
   375  	installation, ok := m.allInstallations.Load(m.installationID)
   376  	if !ok {
   377  		return nil, errors.New("no installation found")
   378  	}
   379  
   380  	if installation.InstallationMetadata == nil {
   381  		return nil, errors.New("no installation metadata")
   382  	}
   383  
   384  	return installation.InstallationMetadata, nil
   385  }
   386  
   387  func (m *Messenger) prepareSyncKeypairMessage(kp *accounts.Keypair) (*protobuf.SyncKeypair, error) {
   388  	message := &protobuf.SyncKeypair{
   389  		Clock:                   kp.Clock,
   390  		KeyUid:                  kp.KeyUID,
   391  		Name:                    kp.Name,
   392  		Type:                    kp.Type.String(),
   393  		DerivedFrom:             kp.DerivedFrom,
   394  		LastUsedDerivationIndex: kp.LastUsedDerivationIndex,
   395  		SyncedFrom:              kp.SyncedFrom,
   396  		Removed:                 kp.Removed,
   397  	}
   398  
   399  	if kp.SyncedFrom == "" {
   400  		installationMetadata, err := m.getMyInstallationMetadata()
   401  		if err != nil {
   402  			return nil, err
   403  		}
   404  		message.SyncedFrom = installationMetadata.Name
   405  	}
   406  
   407  	for _, acc := range kp.Accounts {
   408  		sAcc := m.prepareSyncAccountMessage(acc)
   409  		if sAcc == nil {
   410  			continue
   411  		}
   412  
   413  		message.Accounts = append(message.Accounts, sAcc)
   414  	}
   415  
   416  	syncKcMsgs, err := m.prepareSyncKeycardsMessage(kp.KeyUID)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	message.Keycards = syncKcMsgs
   421  
   422  	if m.walletAPI != nil {
   423  		message.KeycardPairings, err = m.walletAPI.GetPairingsJSONFileContent()
   424  		if err != nil {
   425  			return nil, err
   426  		}
   427  	}
   428  
   429  	return message, nil
   430  }
   431  
   432  func (m *Messenger) UpdateTokenPreferences(preferences []walletsettings.TokenPreferences) error {
   433  	clock, _ := m.getLastClockWithRelatedChat()
   434  	testNetworksEnabled, err := m.settings.GetTestNetworksEnabled()
   435  	if err != nil {
   436  		return err
   437  	}
   438  
   439  	groupByCommunity, err := m.settings.GetTokenGroupByCommunity()
   440  	if err != nil {
   441  		return err
   442  	}
   443  
   444  	err = m.settings.UpdateTokenPreferences(preferences, groupByCommunity, testNetworksEnabled, clock)
   445  	if err != nil {
   446  		return err
   447  	}
   448  
   449  	return m.syncTokenPreferences(m.dispatchMessage)
   450  }
   451  
   452  func (m *Messenger) GetTokenPreferences() ([]walletsettings.TokenPreferences, error) {
   453  	testNetworksEnabled, err := m.settings.GetTestNetworksEnabled()
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  
   458  	list, err := m.settings.GetTokenPreferences(testNetworksEnabled)
   459  	if err != nil {
   460  		return nil, err
   461  	}
   462  	return list, nil
   463  }
   464  
   465  func (m *Messenger) prepareTokenPreferencesMessage(pref walletsettings.TokenPreferences) *protobuf.TokenPreferences {
   466  	return &protobuf.TokenPreferences{
   467  		Key:           pref.Key,
   468  		Position:      int64(pref.Position),
   469  		GroupPosition: int64(pref.GroupPosition),
   470  		Visible:       pref.Visible,
   471  		CommunityId:   pref.CommunityID,
   472  	}
   473  }
   474  
   475  func (m *Messenger) syncTokenPreferences(rawMessageHandler RawMessageHandler) error {
   476  	if !m.hasPairedDevices() {
   477  		return nil
   478  	}
   479  
   480  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   481  	defer cancel()
   482  
   483  	_, chat := m.getLastClockWithRelatedChat()
   484  
   485  	lastUpdate, err := m.settings.GetClockOfLastTokenPreferencesChange()
   486  	if err != nil {
   487  		return err
   488  	}
   489  
   490  	testNetworksEnabled, err := m.settings.GetTestNetworksEnabled()
   491  	if err != nil {
   492  		return err
   493  	}
   494  
   495  	preferences, err := m.GetTokenPreferences()
   496  	if err != nil {
   497  		return err
   498  	}
   499  
   500  	message := &protobuf.SyncTokenPreferences{
   501  		Clock:   lastUpdate,
   502  		Testnet: testNetworksEnabled,
   503  	}
   504  
   505  	for _, pref := range preferences {
   506  		message.Preferences = append(message.Preferences, m.prepareTokenPreferencesMessage(pref))
   507  	}
   508  
   509  	encodedMessage, err := proto.Marshal(message)
   510  	if err != nil {
   511  		return err
   512  	}
   513  
   514  	rawMessage := common.RawMessage{
   515  		LocalChatID: chat.ID,
   516  		Payload:     encodedMessage,
   517  		MessageType: protobuf.ApplicationMetadataMessage_SYNC_TOKEN_PREFERENCES,
   518  		ResendType:  common.ResendTypeDataSync,
   519  	}
   520  
   521  	_, err = rawMessageHandler(ctx, rawMessage)
   522  	return err
   523  }
   524  
   525  func (m *Messenger) UpdateCollectiblePreferences(preferences []walletsettings.CollectiblePreferences) error {
   526  	clock, _ := m.getLastClockWithRelatedChat()
   527  	testNetworksEnabled, err := m.settings.GetTestNetworksEnabled()
   528  	if err != nil {
   529  		return err
   530  	}
   531  
   532  	groupByCommunity, err := m.settings.GetCollectibleGroupByCommunity()
   533  	if err != nil {
   534  		return err
   535  	}
   536  
   537  	groupByCollection, err := m.settings.GetCollectibleGroupByCollection()
   538  	if err != nil {
   539  		return err
   540  	}
   541  
   542  	err = m.settings.UpdateCollectiblePreferences(preferences, groupByCommunity, groupByCollection, testNetworksEnabled, clock)
   543  	if err != nil {
   544  		return err
   545  	}
   546  
   547  	return m.syncCollectiblePreferences(m.dispatchMessage)
   548  }
   549  
   550  func (m *Messenger) GetCollectiblePreferences() ([]walletsettings.CollectiblePreferences, error) {
   551  	testNetworksEnabled, err := m.settings.GetTestNetworksEnabled()
   552  	if err != nil {
   553  		return nil, err
   554  	}
   555  
   556  	list, err := m.settings.GetCollectiblePreferences(testNetworksEnabled)
   557  	if err != nil {
   558  		return nil, err
   559  	}
   560  	return list, nil
   561  }
   562  
   563  func (m *Messenger) prepareCollectiblePreferencesMessage(pref walletsettings.CollectiblePreferences) *protobuf.CollectiblePreferences {
   564  	return &protobuf.CollectiblePreferences{
   565  		Type:     int64(pref.Type),
   566  		Key:      pref.Key,
   567  		Position: int64(pref.Position),
   568  		Visible:  pref.Visible,
   569  	}
   570  }
   571  
   572  func (m *Messenger) syncCollectiblePreferences(rawMessageHandler RawMessageHandler) error {
   573  	if !m.hasPairedDevices() {
   574  		return nil
   575  	}
   576  
   577  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   578  	defer cancel()
   579  
   580  	_, chat := m.getLastClockWithRelatedChat()
   581  
   582  	lastUpdate, err := m.settings.GetClockOfLastCollectiblePreferencesChange()
   583  	if err != nil {
   584  		return err
   585  	}
   586  
   587  	testNetworksEnabled, err := m.settings.GetTestNetworksEnabled()
   588  	if err != nil {
   589  		return err
   590  	}
   591  
   592  	preferences, err := m.GetCollectiblePreferences()
   593  	if err != nil {
   594  		return err
   595  	}
   596  
   597  	message := &protobuf.SyncCollectiblePreferences{
   598  		Clock:   lastUpdate,
   599  		Testnet: testNetworksEnabled,
   600  	}
   601  
   602  	for _, pref := range preferences {
   603  		message.Preferences = append(message.Preferences, m.prepareCollectiblePreferencesMessage(pref))
   604  	}
   605  
   606  	encodedMessage, err := proto.Marshal(message)
   607  	if err != nil {
   608  		return err
   609  	}
   610  
   611  	rawMessage := common.RawMessage{
   612  		LocalChatID: chat.ID,
   613  		Payload:     encodedMessage,
   614  		MessageType: protobuf.ApplicationMetadataMessage_SYNC_COLLECTIBLE_PREFERENCES,
   615  		ResendType:  common.ResendTypeDataSync,
   616  	}
   617  
   618  	_, err = rawMessageHandler(ctx, rawMessage)
   619  	return err
   620  }
   621  
   622  func (m *Messenger) syncAccountsPositions(rawMessageHandler RawMessageHandler) error {
   623  	if !m.hasPairedDevices() {
   624  		return nil
   625  	}
   626  
   627  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   628  	defer cancel()
   629  
   630  	_, chat := m.getLastClockWithRelatedChat()
   631  
   632  	allDbAccounts, err := m.settings.GetActiveAccounts()
   633  	if err != nil {
   634  		return err
   635  	}
   636  
   637  	lastUpdate, err := m.settings.GetClockOfLastAccountsPositionChange()
   638  	if err != nil {
   639  		return err
   640  	}
   641  
   642  	message := &protobuf.SyncAccountsPositions{
   643  		Clock: lastUpdate,
   644  	}
   645  
   646  	for _, acc := range allDbAccounts {
   647  		if acc.Chat {
   648  			continue
   649  		}
   650  		message.Accounts = append(message.Accounts, m.prepareSyncAccountMessage(acc))
   651  	}
   652  
   653  	encodedMessage, err := proto.Marshal(message)
   654  	if err != nil {
   655  		return err
   656  	}
   657  
   658  	rawMessage := common.RawMessage{
   659  		LocalChatID: chat.ID,
   660  		Payload:     encodedMessage,
   661  		MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACCOUNTS_POSITIONS,
   662  		ResendType:  common.ResendTypeDataSync,
   663  	}
   664  
   665  	_, err = rawMessageHandler(ctx, rawMessage)
   666  	return err
   667  }
   668  
   669  func (m *Messenger) syncWalletAccount(acc *accounts.Account, rawMessageHandler RawMessageHandler) error {
   670  	if !m.hasPairedDevices() {
   671  		return nil
   672  	}
   673  
   674  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   675  	defer cancel()
   676  
   677  	_, chat := m.getLastClockWithRelatedChat()
   678  
   679  	message := m.prepareSyncAccountMessage(acc)
   680  
   681  	encodedMessage, err := proto.Marshal(message)
   682  	if err != nil {
   683  		return err
   684  	}
   685  
   686  	rawMessage := common.RawMessage{
   687  		LocalChatID: chat.ID,
   688  		Payload:     encodedMessage,
   689  		MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACCOUNT,
   690  		ResendType:  common.ResendTypeDataSync,
   691  	}
   692  
   693  	_, err = rawMessageHandler(ctx, rawMessage)
   694  	return err
   695  }
   696  
   697  func (m *Messenger) syncKeypair(keypair *accounts.Keypair, rawMessageHandler RawMessageHandler) (err error) {
   698  	if !m.hasPairedDevices() {
   699  		return nil
   700  	}
   701  
   702  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   703  	defer cancel()
   704  
   705  	_, chat := m.getLastClockWithRelatedChat()
   706  	rawMessage := common.RawMessage{
   707  		LocalChatID: chat.ID,
   708  		ResendType:  common.ResendTypeDataSync,
   709  		MessageType: protobuf.ApplicationMetadataMessage_SYNC_KEYPAIR,
   710  	}
   711  
   712  	message, err := m.prepareSyncKeypairMessage(keypair)
   713  	if err != nil {
   714  		return err
   715  	}
   716  
   717  	rawMessage.Payload, err = proto.Marshal(message)
   718  	if err != nil {
   719  		return err
   720  	}
   721  
   722  	_, err = rawMessageHandler(ctx, rawMessage)
   723  	return err
   724  }
   725  
   726  // This function resolves which protobuf message needs to be sent.
   727  //
   728  // If `KeyUID` is empty (means it's a watch only account) we send `protobuf.SyncAccount` message
   729  // otherwise means the account belong to a keypai, hence we send `protobuf.SyncKeypair` message
   730  func (m *Messenger) resolveAndSyncKeypairOrJustWalletAccount(keyUID string, address types.Address, clock uint64, rawMessageHandler RawMessageHandler) error {
   731  	if !m.hasPairedDevices() {
   732  		return nil
   733  	}
   734  
   735  	if keyUID == "" {
   736  		var dbAccount *accounts.Account
   737  		allDbAccounts, err := m.settings.GetAllAccounts() // removed accounts included
   738  		if err != nil {
   739  			return err
   740  		}
   741  
   742  		for _, acc := range allDbAccounts {
   743  			if acc.Address == address {
   744  				dbAccount = acc
   745  				break
   746  			}
   747  		}
   748  
   749  		if dbAccount == nil {
   750  			return accounts.ErrDbAccountNotFound
   751  		}
   752  
   753  		err = m.syncWalletAccount(dbAccount, rawMessageHandler)
   754  		if err != nil {
   755  			return err
   756  		}
   757  	} else {
   758  		var dbKeypair *accounts.Keypair
   759  		allDbKeypairs, err := m.settings.GetAllKeypairs() // removed keypairs included
   760  		if err != nil {
   761  			return err
   762  		}
   763  
   764  		for _, kp := range allDbKeypairs {
   765  			if kp.KeyUID == keyUID {
   766  				dbKeypair = kp
   767  				break
   768  			}
   769  		}
   770  
   771  		if dbKeypair == nil {
   772  			return accounts.ErrDbKeypairNotFound
   773  		}
   774  
   775  		err = m.syncKeypair(dbKeypair, rawMessageHandler)
   776  		if err != nil {
   777  			return err
   778  		}
   779  	}
   780  
   781  	_, chat := m.getLastClockWithRelatedChat()
   782  	chat.LastClockValue = clock
   783  	return m.saveChat(chat)
   784  }
   785  
   786  func (m *Messenger) RemainingAccountCapacity() (int, error) {
   787  	accounts, err := m.settings.GetActiveAccounts()
   788  	if err != nil {
   789  		return 0, err
   790  	}
   791  	numOfAccountsWithoutChatAccount := 0
   792  	if len(accounts) > 0 {
   793  		numOfAccountsWithoutChatAccount = len(accounts) - 1
   794  	}
   795  	remainingCapacity := constants.MaxNumberOfAccounts - numOfAccountsWithoutChatAccount
   796  	if remainingCapacity <= 0 {
   797  		return 0, errors.New("no more accounts can be added")
   798  	}
   799  
   800  	return remainingCapacity, nil
   801  }
   802  
   803  func (m *Messenger) RemainingKeypairCapacity() (int, error) {
   804  	keypairs, err := m.settings.GetActiveKeypairs()
   805  	if err != nil {
   806  		return 0, err
   807  	}
   808  	remainingCapacity := constants.MaxNumberOfKeypairs - len(keypairs)
   809  	if remainingCapacity <= 0 {
   810  		return 0, errors.New("no more keypairs can be added")
   811  	}
   812  
   813  	return remainingCapacity, nil
   814  }
   815  
   816  func (m *Messenger) RemainingWatchOnlyAccountCapacity() (int, error) {
   817  	accounts, err := m.settings.GetActiveWatchOnlyAccounts()
   818  	if err != nil {
   819  		return 0, err
   820  	}
   821  	remainingCapacity := constants.MaxNumberOfWatchOnlyAccounts - len(accounts)
   822  	if remainingCapacity <= 0 {
   823  		return 0, errors.New("no more watch-only accounts can be added")
   824  	}
   825  	return remainingCapacity, nil
   826  }