github.com/status-im/status-go@v1.1.0/services/wallet/saved_addresses.go (about) 1 package wallet 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "time" 9 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/status-im/status-go/constants" 12 multiAccCommon "github.com/status-im/status-go/multiaccounts/common" 13 ) 14 15 type savedAddressMeta struct { 16 UpdateClock uint64 // wall clock used to deconflict concurrent updates 17 } 18 19 type SavedAddress struct { 20 Address common.Address `json:"address"` 21 // TODO: Add Emoji 22 // Emoji string `json:"emoji"` 23 Name string `json:"name"` 24 ChainShortNames string `json:"chainShortNames"` // used with address only, not with ENSName 25 ENSName string `json:"ens"` 26 ColorID multiAccCommon.CustomizationColor `json:"colorId"` 27 IsTest bool `json:"isTest"` 28 CreatedAt int64 `json:"createdAt"` 29 Removed bool `json:"removed"` 30 savedAddressMeta 31 } 32 33 func (s *SavedAddress) ID() string { 34 return fmt.Sprintf("%s-%t", s.Address.Hex(), s.IsTest) 35 } 36 37 func (s *SavedAddress) MarshalJSON() ([]byte, error) { 38 item := struct { 39 Address common.Address `json:"address"` 40 MixedcaseAddress string `json:"mixedcaseAddress"` 41 Name string `json:"name"` 42 ChainShortNames string `json:"chainShortNames"` 43 ENSName string `json:"ens"` 44 ColorID multiAccCommon.CustomizationColor `json:"colorId"` 45 IsTest bool `json:"isTest"` 46 CreatedAt int64 `json:"createdAt"` 47 Removed bool `json:"removed"` 48 }{ 49 Address: s.Address, 50 MixedcaseAddress: s.Address.Hex(), 51 Name: s.Name, 52 ChainShortNames: s.ChainShortNames, 53 ENSName: s.ENSName, 54 ColorID: s.ColorID, 55 IsTest: s.IsTest, 56 CreatedAt: s.CreatedAt, 57 Removed: s.Removed, 58 } 59 60 return json.Marshal(item) 61 } 62 63 type SavedAddressesManager struct { 64 db *sql.DB 65 } 66 67 func NewSavedAddressesManager(db *sql.DB) *SavedAddressesManager { 68 return &SavedAddressesManager{db: db} 69 } 70 71 const rawQueryColumnsOrder = "address, name, removed, update_clock, chain_short_names, ens_name, is_test, created_at, color" 72 73 // getSavedAddressesFromDBRows retrieves all data based on SELECT Query using rawQueryColumnsOrder 74 func getSavedAddressesFromDBRows(rows *sql.Rows) ([]*SavedAddress, error) { 75 var addresses []*SavedAddress 76 for rows.Next() { 77 sa := &SavedAddress{} 78 // based on rawQueryColumnsOrder 79 err := rows.Scan( 80 &sa.Address, 81 &sa.Name, 82 &sa.Removed, 83 &sa.UpdateClock, 84 &sa.ChainShortNames, 85 &sa.ENSName, 86 &sa.IsTest, 87 &sa.CreatedAt, 88 &sa.ColorID, 89 ) 90 if err != nil { 91 return nil, err 92 } 93 94 addresses = append(addresses, sa) 95 } 96 97 return addresses, nil 98 } 99 100 func (sam *SavedAddressesManager) getSavedAddresses(condition string) ([]*SavedAddress, error) { 101 var whereCondition string 102 if condition != "" { 103 whereCondition = fmt.Sprintf("WHERE %s", condition) 104 } 105 106 rows, err := sam.db.Query(fmt.Sprintf("SELECT %s FROM saved_addresses %s", rawQueryColumnsOrder, whereCondition)) 107 if err != nil { 108 return nil, err 109 } 110 defer rows.Close() 111 112 addresses, err := getSavedAddressesFromDBRows(rows) 113 return addresses, err 114 } 115 116 func (sam *SavedAddressesManager) GetSavedAddresses() ([]*SavedAddress, error) { 117 return sam.getSavedAddresses("removed != 1") 118 } 119 120 func (sam *SavedAddressesManager) GetSavedAddressesPerMode(testnetMode bool) ([]*SavedAddress, error) { 121 return sam.getSavedAddresses(fmt.Sprintf("is_test = %t AND removed != 1", testnetMode)) 122 } 123 124 // GetRawSavedAddresses provides access to the soft-delete and sync metadata 125 func (sam *SavedAddressesManager) GetRawSavedAddresses() ([]*SavedAddress, error) { 126 return sam.getSavedAddresses("") 127 } 128 129 func (sam *SavedAddressesManager) RemainingCapacityForSavedAddresses(testnetMode bool) (int, error) { 130 savedAddress, err := sam.GetSavedAddressesPerMode(testnetMode) 131 if err != nil { 132 return 0, err 133 } 134 remainingCapacity := constants.MaxNumberOfSavedAddresses - len(savedAddress) 135 if remainingCapacity <= 0 { 136 return 0, errors.New("no more save addresses can be added") 137 } 138 139 return remainingCapacity, nil 140 } 141 142 func (sam *SavedAddressesManager) upsertSavedAddress(sa SavedAddress, tx *sql.Tx) (err error) { 143 if tx == nil { 144 tx, err = sam.db.Begin() 145 if err != nil { 146 return err 147 } 148 defer func() { 149 if err == nil { 150 err = tx.Commit() 151 return 152 } 153 _ = tx.Rollback() 154 }() 155 } 156 rows, err := tx.Query( 157 fmt.Sprintf("SELECT %s FROM saved_addresses WHERE address = ? AND is_test = ?", rawQueryColumnsOrder), 158 sa.Address, sa.IsTest, 159 ) 160 if err != nil { 161 return err 162 } 163 defer rows.Close() 164 savedAddresses, err := getSavedAddressesFromDBRows(rows) 165 if err != nil { 166 return err 167 } 168 sa.CreatedAt = time.Now().Unix() 169 for _, savedAddress := range savedAddresses { 170 if savedAddress.Address == sa.Address && savedAddress.IsTest == sa.IsTest { 171 sa.CreatedAt = savedAddress.CreatedAt 172 break 173 } 174 } 175 sqlStatement := ` 176 INSERT OR REPLACE 177 INTO 178 saved_addresses ( 179 address, 180 name, 181 removed, 182 update_clock, 183 chain_short_names, 184 ens_name, 185 is_test, 186 created_at, 187 color 188 ) 189 VALUES 190 (?, ?, ?, ?, ?, ?, ?, ?, ?) 191 ` 192 insert, err := tx.Prepare(sqlStatement) 193 if err != nil { 194 return err 195 } 196 defer insert.Close() 197 _, err = insert.Exec(sa.Address, sa.Name, sa.Removed, sa.UpdateClock, sa.ChainShortNames, sa.ENSName, 198 sa.IsTest, sa.CreatedAt, sa.ColorID) 199 return err 200 } 201 202 func (sam *SavedAddressesManager) UpdateMetadataAndUpsertSavedAddress(sa SavedAddress) error { 203 return sam.upsertSavedAddress(sa, nil) 204 } 205 206 func (sam *SavedAddressesManager) startTransactionAndCheckIfNewerChange(address common.Address, isTest bool, updateClock uint64) (newer bool, tx *sql.Tx, err error) { 207 tx, err = sam.db.Begin() 208 if err != nil { 209 return false, nil, err 210 } 211 row := tx.QueryRow("SELECT update_clock FROM saved_addresses WHERE address = ? AND is_test = ?", address, isTest) 212 if err != nil { 213 return false, tx, err 214 } 215 216 var dbUpdateClock uint64 217 err = row.Scan(&dbUpdateClock) 218 if err != nil { 219 return err == sql.ErrNoRows, tx, err 220 } 221 return dbUpdateClock < updateClock, tx, nil 222 } 223 224 func (sam *SavedAddressesManager) AddSavedAddressIfNewerUpdate(sa SavedAddress) (insertedOrUpdated bool, err error) { 225 newer, tx, err := sam.startTransactionAndCheckIfNewerChange(sa.Address, sa.IsTest, sa.UpdateClock) 226 defer func() { 227 if err == nil { 228 err = tx.Commit() 229 return 230 } 231 _ = tx.Rollback() 232 }() 233 if !newer { 234 return false, err 235 } 236 237 err = sam.upsertSavedAddress(sa, tx) 238 if err != nil { 239 return false, err 240 } 241 242 return true, err 243 } 244 245 func (sam *SavedAddressesManager) DeleteSavedAddress(address common.Address, isTest bool, updateClock uint64) (deleted bool, err error) { 246 if err != nil { 247 return false, err 248 } 249 newer, tx, err := sam.startTransactionAndCheckIfNewerChange(address, isTest, updateClock) 250 defer func() { 251 if err == nil { 252 err = tx.Commit() 253 return 254 } 255 _ = tx.Rollback() 256 }() 257 if !newer { 258 return false, err 259 } 260 261 update, err := tx.Prepare(`UPDATE saved_addresses SET removed = 1, update_clock = ? WHERE address = ? AND is_test = ?`) 262 if err != nil { 263 return false, err 264 } 265 defer update.Close() 266 res, err := update.Exec(updateClock, address, isTest) 267 if err != nil { 268 return false, err 269 } 270 271 nRows, err := res.RowsAffected() 272 if err != nil { 273 return false, err 274 } 275 276 return nRows > 0, nil 277 } 278 279 func (sam *SavedAddressesManager) DeleteSoftRemovedSavedAddresses(threshold uint64) error { 280 _, err := sam.db.Exec(`DELETE FROM saved_addresses WHERE removed = 1 AND update_clock < ?`, threshold) 281 return err 282 }