status-go/protocol/messenger_wallet.go

305 lines
7.2 KiB
Go

package protocol
import (
"context"
"errors"
"time"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf"
)
var (
checkBalancesInterval = time.Minute * 10
ErrCannotChangeKeypairName = errors.New("cannot change profile keypair name")
)
func (m *Messenger) retrieveWalletBalances() error {
if m.walletAPI == nil {
m.logger.Warn("wallet api not enabled")
}
accounts, err := m.settings.GetAccounts()
if err != nil {
return err
}
if len(accounts) == 0 {
m.logger.Info("no accounts to sync wallet balance")
}
var ethAccounts []ethcommon.Address
for _, acc := range accounts {
m.logger.Info("syncing wallet address", zap.String("account", acc.Address.Hex()))
ethAccounts = append(ethAccounts, ethcommon.BytesToAddress(acc.Address.Bytes()))
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
defer cancel()
// TODO: publish tokens as a signal
_, err = m.walletAPI.GetWalletToken(ctx, ethAccounts)
if err != nil {
return err
}
return nil
}
func (m *Messenger) watchWalletBalances() {
m.logger.Info("watching wallet balances")
if m.walletAPI == nil {
m.logger.Warn("wallet service not enabled")
return
}
go func() {
for {
select {
case <-time.After(checkBalancesInterval):
err := m.retrieveWalletBalances()
if err != nil {
m.logger.Error("failed to retrieve wallet balances", zap.Error(err))
}
case <-m.quit:
return
}
}
}()
}
func (m *Messenger) UpdateKeypairName(keyUID string, name string) error {
if keyUID == m.account.KeyUID && name != m.account.Name {
// profile keypair name must always follow profile display name
return ErrCannotChangeKeypairName
}
clock, _ := m.getLastClockWithRelatedChat()
err := m.settings.UpdateKeypairName(keyUID, name, clock)
if err != nil {
return err
}
dbKeypair, err := m.settings.GetKeypairByKeyUID(m.account.KeyUID)
if err != nil {
return err
}
return m.syncKeypair(dbKeypair, false, m.dispatchMessage)
}
func (m *Messenger) SaveOrUpdateKeypair(keypair *accounts.Keypair) error {
if keypair.KeyUID == m.account.KeyUID && keypair.Name != m.account.Name {
// profile keypair name must always follow profile display name
return ErrCannotChangeKeypairName
}
clock, _ := m.getLastClockWithRelatedChat()
keypair.Clock = clock
for _, acc := range keypair.Accounts {
acc.Clock = clock
}
err := m.settings.SaveOrUpdateKeypair(keypair)
if err != nil {
return err
}
return m.syncKeypair(keypair, false, m.dispatchMessage)
}
func (m *Messenger) SaveOrUpdateAccount(acc *accounts.Account) error {
clock, _ := m.getLastClockWithRelatedChat()
acc.Clock = clock
err := m.settings.SaveOrUpdateAccounts([]*accounts.Account{acc})
if err != nil {
return err
}
return m.syncWalletAccount(acc, m.dispatchMessage)
}
func (m *Messenger) DeleteAccount(address types.Address) error {
acc, err := m.settings.GetAccountByAddress(address)
if err != nil {
return err
}
err = m.settings.DeleteAccount(address)
if err != nil {
return err
}
clock, chat := m.getLastClockWithRelatedChat()
acc.Clock = clock
acc.Removed = true
err = m.syncWalletAccount(acc, m.dispatchMessage)
if err != nil {
return err
}
chat.LastClockValue = clock
return m.saveChat(chat)
}
func (m *Messenger) prepareSyncAccountMessage(acc *accounts.Account) *protobuf.SyncAccount {
if acc.Chat {
return nil
}
return &protobuf.SyncAccount{
Clock: acc.Clock,
Address: acc.Address.Bytes(),
KeyUid: acc.KeyUID,
PublicKey: acc.PublicKey,
Path: acc.Path,
Name: acc.Name,
ColorId: string(acc.ColorID),
Emoji: acc.Emoji,
Wallet: acc.Wallet,
Chat: acc.Chat,
Hidden: acc.Hidden,
Removed: acc.Removed,
}
}
func (m *Messenger) getMyInstallationMetadata() (*multidevice.InstallationMetadata, error) {
installation, ok := m.allInstallations.Load(m.installationID)
if !ok {
return nil, errors.New("no installation found")
}
if installation.InstallationMetadata == nil {
return nil, errors.New("no installation metadata")
}
return installation.InstallationMetadata, nil
}
func (m *Messenger) prepareSyncKeypairMessage(kp *accounts.Keypair) (*protobuf.SyncKeypair, error) {
message := &protobuf.SyncKeypair{
Clock: kp.Clock,
KeyUid: kp.KeyUID,
Name: kp.Name,
Type: kp.Type.String(),
DerivedFrom: kp.DerivedFrom,
LastUsedDerivationIndex: kp.LastUsedDerivationIndex,
SyncedFrom: kp.SyncedFrom,
}
if kp.SyncedFrom == "" {
installationMetadata, err := m.getMyInstallationMetadata()
if err != nil {
return nil, err
}
message.SyncedFrom = installationMetadata.Name
}
for _, acc := range kp.Accounts {
sAcc := m.prepareSyncAccountMessage(acc)
if sAcc == nil {
continue
}
message.Accounts = append(message.Accounts, sAcc)
}
return message, nil
}
func (m *Messenger) prepareSyncKeypairFullMessage(kp *accounts.Keypair) (*protobuf.SyncKeypairFull, error) {
syncKpMsg, err := m.prepareSyncKeypairMessage(kp)
if err != nil {
return nil, err
}
syncKcMsgs, err := m.prepareSyncKeycardsMessage(kp.KeyUID)
if err != nil {
return nil, err
}
return &protobuf.SyncKeypairFull{
Keypair: syncKpMsg,
Keycards: syncKcMsgs,
}, nil
}
func (m *Messenger) syncWalletAccount(acc *accounts.Account, rawMessageHandler RawMessageHandler) error {
if !m.hasPairedDevices() {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, chat := m.getLastClockWithRelatedChat()
message := m.prepareSyncAccountMessage(acc)
encodedMessage, err := proto.Marshal(message)
if err != nil {
return err
}
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACCOUNT,
ResendAutomatically: true,
}
_, err = rawMessageHandler(ctx, rawMessage)
return err
}
func (m *Messenger) syncKeypair(keypair *accounts.Keypair, fullKeypairSync bool, rawMessageHandler RawMessageHandler) (err error) {
if !m.hasPairedDevices() {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, chat := m.getLastClockWithRelatedChat()
rawMessage := common.RawMessage{
LocalChatID: chat.ID,
ResendAutomatically: true,
}
if fullKeypairSync {
message, err := m.prepareSyncKeypairFullMessage(keypair)
if err != nil {
return err
}
rawMessage.MessageType = protobuf.ApplicationMetadataMessage_SYNC_FULL_KEYPAIR
rawMessage.Payload, err = proto.Marshal(message)
if err != nil {
return err
}
} else {
message, err := m.prepareSyncKeypairMessage(keypair)
if err != nil {
return err
}
rawMessage.MessageType = protobuf.ApplicationMetadataMessage_SYNC_KEYPAIR
rawMessage.Payload, err = proto.Marshal(message)
if err != nil {
return err
}
}
_, err = rawMessageHandler(ctx, rawMessage)
return err
}