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 }