feat: implement collectible connection status

This commit is contained in:
Dario Gabriel Lipicar 2023-09-22 10:18:42 -03:00 committed by dlipicar
parent ba1f8ba923
commit bd6f9b098b
16 changed files with 433 additions and 100 deletions

View File

@ -14,19 +14,24 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/connection"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
"github.com/status-im/status-go/services/wallet/walletevent"
)
const requestTimeout = 5 * time.Second
const hystrixContractOwnershipClientName = "contractOwnershipClient"
const EventCollectiblesConnectionStatusChanged walletevent.EventType = "wallet-collectible-status-changed"
// ERC721 does not support function "TokenURI" if call
// returns error starting with one of these strings
var noTokenURIErrorPrefixes = []string{
@ -35,6 +40,7 @@ var noTokenURIErrorPrefixes = []string{
}
var (
ErrAllProvidersFailedForChainID = errors.New("all providers failed for chainID")
ErrNoProvidersAvailableForChainID = errors.New("no providers available for chainID")
)
@ -48,12 +54,18 @@ type Manager struct {
accountOwnershipProviders []thirdparty.CollectibleAccountOwnershipProvider
collectibleDataProviders []thirdparty.CollectibleDataProvider
collectionDataProviders []thirdparty.CollectionDataProvider
collectibleProviders []thirdparty.CollectibleProvider
metadataProvider thirdparty.CollectibleMetadataProvider
communityInfoProvider thirdparty.CollectibleCommunityInfoProvider
opensea *opensea.Client
httpClient *http.Client
collectiblesDataDB *CollectibleDataDB
collectionsDataDB *CollectionDataDB
opensea *opensea.Client
httpClient *http.Client
collectiblesDataDB *CollectibleDataDB
collectionsDataDB *CollectionDataDB
statuses map[string]*connection.Status
statusNotifier *connection.StatusNotifier
}
func NewManager(
@ -63,7 +75,8 @@ func NewManager(
accountOwnershipProviders []thirdparty.CollectibleAccountOwnershipProvider,
collectibleDataProviders []thirdparty.CollectibleDataProvider,
collectionDataProviders []thirdparty.CollectionDataProvider,
opensea *opensea.Client) *Manager {
opensea *opensea.Client,
feed *event.Feed) *Manager {
hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{
Timeout: 10000,
MaxConcurrentRequests: 100,
@ -71,18 +84,62 @@ func NewManager(
ErrorPercentThreshold: 25,
})
ownershipDB := NewOwnershipDB(db)
statuses := make(map[string]*connection.Status)
allChainIDs := walletCommon.AllChainIDs()
for _, chainID := range allChainIDs {
status := connection.NewStatus()
state := status.GetState()
latestUpdateTimestamp, err := ownershipDB.GetLatestOwnershipUpdateTimestamp(chainID)
if err == nil {
state.LastSuccessAt = latestUpdateTimestamp
status.SetState(state)
}
statuses[chainID.String()] = status
}
statusNotifier := connection.NewStatusNotifier(
statuses,
EventCollectiblesConnectionStatusChanged,
feed,
)
// Get list of all providers
collectibleProvidersMap := make(map[string]thirdparty.CollectibleProvider)
collectibleProviders := make([]thirdparty.CollectibleProvider, 0)
for _, provider := range contractOwnershipProviders {
collectibleProvidersMap[provider.ID()] = provider
}
for _, provider := range accountOwnershipProviders {
collectibleProvidersMap[provider.ID()] = provider
}
for _, provider := range collectibleDataProviders {
collectibleProvidersMap[provider.ID()] = provider
}
for _, provider := range collectionDataProviders {
collectibleProvidersMap[provider.ID()] = provider
}
for _, provider := range collectibleProvidersMap {
collectibleProviders = append(collectibleProviders, provider)
}
return &Manager{
rpcClient: rpcClient,
contractOwnershipProviders: contractOwnershipProviders,
accountOwnershipProviders: accountOwnershipProviders,
collectibleDataProviders: collectibleDataProviders,
collectionDataProviders: collectionDataProviders,
collectibleProviders: collectibleProviders,
opensea: opensea,
httpClient: &http.Client{
Timeout: requestTimeout,
},
collectiblesDataDB: NewCollectibleDataDB(db),
collectionsDataDB: NewCollectionDataDB(db),
statuses: statuses,
statusNotifier: statusNotifier,
}
}
@ -216,14 +273,19 @@ func (o *Manager) FetchBalancesByOwnerAndContractAddress(chainID walletCommon.Ch
}
func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(chainID walletCommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
defer o.checkConnectionStatus(chainID)
anyProviderAvailable := false
for _, provider := range o.accountOwnershipProviders {
if !provider.IsChainSupported(chainID) {
continue
}
anyProviderAvailable = true
assetContainer, err := provider.FetchAllAssetsByOwnerAndContractAddress(chainID, owner, contractAddresses, cursor, limit)
if err != nil {
return nil, err
log.Error("FetchAllAssetsByOwnerAndContractAddress failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
continue
}
err = o.processFullCollectibleData(assetContainer.Items)
@ -234,10 +296,16 @@ func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(chainID walletCommon.C
return assetContainer, nil
}
if anyProviderAvailable {
return nil, ErrAllProvidersFailedForChainID
}
return nil, ErrNoProvidersAvailableForChainID
}
func (o *Manager) FetchAllAssetsByOwner(chainID walletCommon.ChainID, owner common.Address, cursor string, limit int) (*thirdparty.FullCollectibleDataContainer, error) {
defer o.checkConnectionStatus(chainID)
anyProviderAvailable := false
for _, provider := range o.accountOwnershipProviders {
if !provider.IsChainSupported(chainID) {
continue
@ -245,7 +313,8 @@ func (o *Manager) FetchAllAssetsByOwner(chainID walletCommon.ChainID, owner comm
assetContainer, err := provider.FetchAllAssetsByOwner(chainID, owner, cursor, limit)
if err != nil {
return nil, err
log.Error("FetchAllAssetsByOwner failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
continue
}
err = o.processFullCollectibleData(assetContainer.Items)
@ -256,6 +325,9 @@ func (o *Manager) FetchAllAssetsByOwner(chainID walletCommon.ChainID, owner comm
return assetContainer, nil
}
if anyProviderAvailable {
return nil, ErrAllProvidersFailedForChainID
}
return nil, ErrNoProvidersAvailableForChainID
}
@ -280,6 +352,8 @@ func (o *Manager) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.Collec
missingIDsPerChainID := thirdparty.GroupCollectibleUIDsByChainID(missingIDs)
for chainID, idsToFetch := range missingIDsPerChainID {
defer o.checkConnectionStatus(chainID)
for _, provider := range o.collectibleDataProviders {
if !provider.IsChainSupported(chainID) {
continue
@ -287,7 +361,8 @@ func (o *Manager) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.Collec
fetchedAssets, err := provider.FetchAssetsByCollectibleUniqueID(idsToFetch)
if err != nil {
return nil, err
log.Error("FetchAssetsByCollectibleUniqueID failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
continue
}
err = o.processFullCollectibleData(fetchedAssets)
@ -311,6 +386,8 @@ func (o *Manager) FetchCollectionsDataByContractID(ids []thirdparty.ContractID)
missingIDsPerChainID := thirdparty.GroupContractIDsByChainID(missingIDs)
for chainID, idsToFetch := range missingIDsPerChainID {
defer o.checkConnectionStatus(chainID)
for _, provider := range o.collectionDataProviders {
if !provider.IsChainSupported(chainID) {
continue
@ -318,7 +395,8 @@ func (o *Manager) FetchCollectionsDataByContractID(ids []thirdparty.ContractID)
fetchedCollections, err := provider.FetchCollectionsDataByContractID(idsToFetch)
if err != nil {
return nil, err
log.Error("FetchCollectionsDataByContractID failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
continue
}
err = o.processCollectionData(fetchedCollections)
@ -362,11 +440,17 @@ func getCollectibleOwnersByContractAddressFunc(chainID walletCommon.ChainID, con
return nil
}
return func() (any, error) {
return provider.FetchCollectibleOwnersByContractAddress(chainID, contractAddress)
res, err := provider.FetchCollectibleOwnersByContractAddress(chainID, contractAddress)
if err != nil {
log.Error("FetchCollectibleOwnersByContractAddress failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
}
return res, err
}
}
func (o *Manager) FetchCollectibleOwnersByContractAddress(chainID walletCommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
defer o.checkConnectionStatus(chainID)
mainProvider, fallbackProvider := o.getContractOwnershipProviders(chainID)
if mainProvider == nil {
return nil, ErrNoProvidersAvailableForChainID
@ -381,7 +465,6 @@ func (o *Manager) FetchCollectibleOwnersByContractAddress(chainID walletCommon.C
}
return owners.(*thirdparty.CollectibleContractOwnership), nil
}
func isMetadataEmpty(asset thirdparty.CollectibleData) bool {
@ -592,3 +675,21 @@ func (o *Manager) FetchCollectibleCommunityTraits(communityID string, id thirdpa
return traits, nil
}
// Reset connection status to trigger notifications
// on the next status update
func (o *Manager) ResetConnectionStatus() {
for _, status := range o.statuses {
status.ResetStateValue()
}
}
func (o *Manager) checkConnectionStatus(chainID walletCommon.ChainID) {
for _, provider := range o.collectibleProviders {
if provider.IsChainSupported(chainID) && provider.IsConnected() {
o.statuses[chainID.String()].SetIsConnected(true)
return
}
}
o.statuses[chainID.String()].SetIsConnected(false)
}

View File

@ -210,3 +210,30 @@ func (o *OwnershipDB) GetOwnershipUpdateTimestamp(owner common.Address, chainID
return timestamp, nil
}
func (o *OwnershipDB) GetLatestOwnershipUpdateTimestamp(chainID walletCommon.ChainID) (int64, error) {
query := `SELECT MAX(timestamp)
FROM collectibles_ownership_update_timestamps
WHERE chain_id = ?`
stmt, err := o.db.Prepare(query)
if err != nil {
return InvalidTimestamp, err
}
defer stmt.Close()
row := stmt.QueryRow(chainID)
var timestamp sql.NullInt64
err = row.Scan(&timestamp)
if err != nil {
return InvalidTimestamp, err
}
if timestamp.Valid {
return timestamp.Int64, nil
}
return InvalidTimestamp, nil
}

View File

@ -23,9 +23,9 @@ func setupOwnershipDBTest(t *testing.T) (*OwnershipDB, func()) {
}
}
func generateTestCollectibles(chainID w_common.ChainID, count int) (result []thirdparty.CollectibleUniqueID) {
func generateTestCollectibles(chainID w_common.ChainID, offset int, count int) (result []thirdparty.CollectibleUniqueID) {
result = make([]thirdparty.CollectibleUniqueID, 0, count)
for i := 0; i < count; i++ {
for i := offset; i < offset+count; i++ {
bigI := big.NewInt(int64(i))
newCollectible := thirdparty.CollectibleUniqueID{
ContractID: thirdparty.ContractID{
@ -43,26 +43,38 @@ func TestUpdateOwnership(t *testing.T) {
oDB, cleanDB := setupOwnershipDBTest(t)
defer cleanDB()
ownerAddress1 := common.HexToAddress("0x1234")
chainID0 := w_common.ChainID(0)
ownedListChain0 := generateTestCollectibles(chainID0, 10)
timestampChain0 := int64(1234567890)
chainID1 := w_common.ChainID(1)
ownedListChain1 := generateTestCollectibles(chainID1, 15)
chainID2 := w_common.ChainID(2)
ownerAddress1 := common.HexToAddress("0x1234")
ownedListChain0 := generateTestCollectibles(chainID0, 0, 10)
timestampChain0 := int64(1234567890)
ownedListChain1 := generateTestCollectibles(chainID1, 0, 15)
timestampChain1 := int64(1234567891)
ownedList1 := append(ownedListChain0, ownedListChain1...)
ownerAddress2 := common.HexToAddress("0x5678")
chainID2 := w_common.ChainID(2)
ownedListChain2 := generateTestCollectibles(chainID2, 20)
ownedListChain2 := generateTestCollectibles(chainID2, 0, 20)
timestampChain2 := int64(1234567892)
ownedList2 := ownedListChain2
fullList := append(ownedList1, ownedList2...)
ownerAddress3 := common.HexToAddress("0xABCD")
ownedListChain1b := generateTestCollectibles(chainID1, len(ownedListChain1), 5)
timestampChain1b := timestampChain1 - 100
ownedListChain2b := generateTestCollectibles(chainID2, len(ownedListChain2), 20)
timestampChain2b := timestampChain2 + 100
randomAddress := common.HexToAddress("0xABCD")
ownedList3 := append(ownedListChain1b, ownedListChain2b...)
allChains := []w_common.ChainID{chainID0, chainID1, chainID2}
allOwnerAddresses := []common.Address{ownerAddress1, ownerAddress2, ownerAddress3}
allCollectibles := append(ownedList1, ownedList2...)
allCollectibles = append(allCollectibles, ownedList3...)
randomAddress := common.HexToAddress("0xFFFF")
var err error
@ -73,14 +85,26 @@ func TestUpdateOwnership(t *testing.T) {
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID0)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID1)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID2)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
err = oDB.Update(chainID0, ownerAddress1, ownedListChain0, timestampChain0)
require.NoError(t, err)
@ -88,49 +112,87 @@ func TestUpdateOwnership(t *testing.T) {
require.NoError(t, err)
require.Equal(t, timestampChain0, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID0)
require.NoError(t, err)
require.Equal(t, timestampChain0, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID1)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID2)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
err = oDB.Update(chainID1, ownerAddress1, ownedListChain1, timestampChain1)
require.NoError(t, err)
err = oDB.Update(chainID2, ownerAddress2, ownedListChain2, timestampChain2)
require.NoError(t, err)
err = oDB.Update(chainID1, ownerAddress3, ownedListChain1b, timestampChain1b)
require.NoError(t, err)
err = oDB.Update(chainID2, ownerAddress3, ownedListChain2b, timestampChain2b)
require.NoError(t, err)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID0)
require.NoError(t, err)
require.Equal(t, timestampChain0, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID0)
require.NoError(t, err)
require.Equal(t, timestampChain0, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1)
require.NoError(t, err)
require.Equal(t, timestampChain1, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress3, chainID1)
require.NoError(t, err)
require.Equal(t, timestampChain1b, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID1)
require.NoError(t, err)
require.Equal(t, timestampChain1, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2)
require.NoError(t, err)
require.Equal(t, timestampChain2, loadedTimestamp)
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{ownerAddress1}, 0, len(fullList))
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress3, chainID2)
require.NoError(t, err)
require.Equal(t, timestampChain2b, loadedTimestamp)
loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID2)
require.NoError(t, err)
require.Equal(t, timestampChain2b, loadedTimestamp)
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{ownerAddress1}, 0, len(allCollectibles))
require.NoError(t, err)
require.Equal(t, ownedListChain0, loadedList)
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0, chainID1}, []common.Address{ownerAddress1, randomAddress}, 0, len(fullList))
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0, chainID1}, []common.Address{ownerAddress1, randomAddress}, 0, len(allCollectibles))
require.NoError(t, err)
require.Equal(t, ownedList1, loadedList)
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID2}, []common.Address{ownerAddress2}, 0, len(fullList))
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID2}, []common.Address{ownerAddress2}, 0, len(allCollectibles))
require.NoError(t, err)
require.Equal(t, ownedList2, loadedList)
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0, chainID1, chainID2}, []common.Address{ownerAddress1, ownerAddress2}, 0, len(fullList))
loadedList, err = oDB.GetOwnedCollectibles(allChains, allOwnerAddresses, 0, len(allCollectibles))
require.NoError(t, err)
require.Equal(t, fullList, loadedList)
require.Equal(t, len(allCollectibles), len(loadedList))
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{randomAddress}, 0, len(fullList))
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{randomAddress}, 0, len(allCollectibles))
require.NoError(t, err)
require.Empty(t, loadedList)
}

View File

@ -190,6 +190,7 @@ func (s *Service) GetCollectiblesDetailsAsync(requestID int32, uniqueIDs []third
func (s *Service) RefetchOwnedCollectibles() {
s.stopPeriodicalOwnershipFetch()
s.manager.ResetConnectionStatus()
_ = s.startPeriodicalOwnershipFetch()
}

View File

@ -1,6 +1,9 @@
package common
import "time"
import (
"strconv"
"time"
)
type ChainID uint64
@ -15,6 +18,22 @@ const (
ArbitrumGoerli uint64 = 421613
)
func (c ChainID) String() string {
return strconv.Itoa(int(c))
}
func AllChainIDs() []ChainID {
return []ChainID{
ChainID(EthereumMainnet),
ChainID(EthereumGoerli),
ChainID(EthereumSepolia),
ChainID(OptimismMainnet),
ChainID(OptimismGoerli),
ChainID(ArbitrumMainnet),
ChainID(ArbitrumGoerli),
}
}
var AverageBlockDurationForChain = map[ChainID]time.Duration{
ChainID(UnknownChainID): time.Duration(12000) * time.Millisecond,
ChainID(EthereumMainnet): time.Duration(12000) * time.Millisecond,

View File

@ -3,54 +3,71 @@ package connection
import (
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/services/wallet/walletevent"
)
type StateChangeCb func(State)
type Status struct {
eventType walletevent.EventType
feed *event.Feed
isConnected bool
lastCheckedAt int64
isConnectedLock sync.RWMutex
stateChangeCb StateChangeCb
state State
stateLock sync.RWMutex
}
func NewStatus(eventType walletevent.EventType, feed *event.Feed) *Status {
func NewStatus() *Status {
return &Status{
eventType: eventType,
feed: feed,
isConnected: true,
lastCheckedAt: time.Now().Unix(),
state: NewState(),
}
}
func (c *Status) SetStateChangeCb(stateChangeCb StateChangeCb) {
c.stateChangeCb = stateChangeCb
}
func (c *Status) SetIsConnected(value bool) {
c.isConnectedLock.Lock()
defer c.isConnectedLock.Unlock()
now := time.Now().Unix()
c.lastCheckedAt = time.Now().Unix()
if value != c.isConnected {
message := "down"
if value {
message = "up"
}
if c.feed != nil {
c.feed.Send(walletevent.Event{
Type: c.eventType,
Accounts: []common.Address{},
Message: message,
At: time.Now().Unix(),
})
}
state := c.GetState()
state.LastCheckedAt = now
if value {
state.LastSuccessAt = now
}
c.isConnected = value
if value {
state.Value = StateValueConnected
} else {
state.Value = StateValueDisconnected
}
c.SetState(state)
}
func (c *Status) ResetStateValue() {
state := c.GetState()
state.Value = StateValueUnknown
c.SetState(state)
}
func (c *Status) GetState() State {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
return c.state
}
func (c *Status) SetState(state State) {
c.stateLock.Lock()
isStateChange := c.state.Value != state.Value
c.state = state
c.stateLock.Unlock()
if isStateChange && c.stateChangeCb != nil {
c.stateChangeCb(state)
}
}
func (c *Status) GetStateValue() StateValue {
return c.GetState().Value
}
func (c *Status) IsConnected() bool {
c.isConnectedLock.RLock()
defer c.isConnectedLock.RUnlock()
return c.isConnected
return c.GetStateValue() == StateValueConnected
}

View File

@ -0,0 +1,60 @@
package connection
import (
"encoding/json"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/status-im/status-go/services/wallet/walletevent"
)
// Client expects a single event with all states
type StatusNotification map[string]State // id -> State
type StatusNotifier struct {
statuses map[string]*Status // id -> Status
eventType walletevent.EventType
feed *event.Feed
}
func NewStatusNotifier(statuses map[string]*Status, eventType walletevent.EventType, feed *event.Feed) *StatusNotifier {
n := StatusNotifier{
statuses: statuses,
eventType: eventType,
feed: feed,
}
for _, status := range statuses {
status.SetStateChangeCb(n.notify)
}
return &n
}
func (n *StatusNotifier) notify(state State) {
// state is ignored, as client expects all valid states in
// a single event, so we fetch them from the map
if n.feed != nil {
statusMap := make(StatusNotification)
for id, status := range n.statuses {
state := status.GetState()
if state.Value == StateValueUnknown {
continue
}
statusMap[id] = state
}
encodedMessage, err := json.Marshal(statusMap)
if err != nil {
return
}
n.feed.Send(walletevent.Event{
Type: n.eventType,
Accounts: []common.Address{},
Message: string(encodedMessage),
At: time.Now().Unix(),
})
}
}

View File

@ -0,0 +1,25 @@
package connection
const InvalidTimestamp = int64(-1)
type StateValue int
const (
StateValueUnknown StateValue = iota
StateValueConnected
StateValueDisconnected
)
type State struct {
Value StateValue `json:"value"`
LastCheckedAt int64 `json:"last_checked_at"`
LastSuccessAt int64 `json:"last_success_at"`
}
func NewState() State {
return State{
Value: StateValueUnknown,
LastCheckedAt: InvalidTimestamp,
LastSuccessAt: InvalidTimestamp,
}
}

View File

@ -8,10 +8,15 @@ import (
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
)
func TestKeycardPairingsFile(t *testing.T) {
service := NewService(nil, nil, &rpc.Client{}, nil, nil, nil, &params.NodeConfig{}, nil, nil, nil, nil)
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
service := NewService(db, nil, &rpc.Client{}, nil, nil, nil, &params.NodeConfig{}, nil, nil, nil, nil)
data, err := service.KeycardPairings().GetPairingsJSONFileContent()
require.NoError(t, err)

View File

@ -109,8 +109,8 @@ func NewService(
blockChainState := NewBlockChainState(rpcClient, accountsDB)
openseaHTTPClient := opensea.NewHTTPClient()
openseaClient := opensea.NewClient(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient, feed)
openseaV2Client := opensea.NewClientV2(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient, feed)
openseaClient := opensea.NewClient(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient)
openseaV2Client := opensea.NewClientV2(config.WalletConfig.OpenseaAPIKey, openseaHTTPClient)
infuraClient := infura.NewClient(config.WalletConfig.InfuraAPIKey, config.WalletConfig.InfuraAPIKeySecret)
alchemyClient := alchemy.NewClient(config.WalletConfig.AlchemyAPIKeys)
@ -140,7 +140,7 @@ func NewService(
alchemyClient,
}
collectiblesManager := collectibles.NewManager(db, rpcClient, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, openseaClient)
collectiblesManager := collectibles.NewManager(db, rpcClient, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, openseaClient, feed)
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, rpcClient.NetworkManager, collectiblesManager)
activity := activity.NewService(db, tokenManager, collectiblesManager, feed)

View File

@ -7,11 +7,11 @@ import (
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/connection"
"github.com/status-im/status-go/services/wallet/thirdparty"
)
@ -48,6 +48,10 @@ func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
return err == nil
}
func (o *Client) IsConnected() bool {
return o.connectionStatus.IsConnected()
}
func getAPIKeySubpath(apiKey string) string {
if apiKey == "" {
return "demo"
@ -67,16 +71,16 @@ func getNFTBaseURL(chainID walletCommon.ChainID, apiKey string) (string, error)
type Client struct {
thirdparty.CollectibleContractOwnershipProvider
client *http.Client
apiKeys map[uint64]string
IsConnected bool
IsConnectedLock sync.RWMutex
client *http.Client
apiKeys map[uint64]string
connectionStatus *connection.Status
}
func NewClient(apiKeys map[uint64]string) *Client {
return &Client{
client: &http.Client{Timeout: time.Minute},
apiKeys: apiKeys,
client: &http.Client{Timeout: time.Minute},
apiKeys: apiKeys,
connectionStatus: connection.NewStatus(),
}
}
@ -136,10 +140,11 @@ func (o *Client) FetchCollectibleOwnersByContractAddress(chainID walletCommon.Ch
url := fmt.Sprintf("%s/getOwnersForContract?%s", baseURL, queryParams.Encode())
resp, err := o.doQuery(url)
if err != nil {
o.connectionStatus.SetIsConnected(false)
return nil, err
}
o.connectionStatus.SetIsConnected(true)
defer resp.Body.Close()
@ -204,8 +209,10 @@ func (o *Client) fetchOwnedAssets(chainID walletCommon.ChainID, owner common.Add
resp, err := o.doQuery(url)
if err != nil {
o.connectionStatus.SetIsConnected(false)
return nil, err
}
o.connectionStatus.SetIsConnected(true)
defer resp.Body.Close()

View File

@ -20,6 +20,7 @@ const FetchFromStartCursor = ""
type CollectibleProvider interface {
ID() string
IsChainSupported(chainID w_common.ChainID) bool
IsConnected() bool
}
type ContractID struct {

View File

@ -6,11 +6,11 @@ import (
"io/ioutil"
"net/http"
"net/url"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/connection"
"github.com/status-im/status-go/services/wallet/thirdparty"
)
@ -18,17 +18,17 @@ const baseURL = "https://nft.api.infura.io"
type Client struct {
thirdparty.CollectibleContractOwnershipProvider
client *http.Client
apiKey string
apiKeySecret string
IsConnected bool
IsConnectedLock sync.RWMutex
client *http.Client
apiKey string
apiKeySecret string
connectionStatus *connection.Status
}
func NewClient(apiKey string, apiKeySecret string) *Client {
return &Client{
client: &http.Client{Timeout: time.Minute},
apiKey: apiKey,
client: &http.Client{Timeout: time.Minute},
apiKey: apiKey,
connectionStatus: connection.NewStatus(),
}
}
@ -62,6 +62,10 @@ func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
return false
}
func (o *Client) IsConnected() bool {
return o.connectionStatus.IsConnected()
}
func (o *Client) FetchCollectibleOwnersByContractAddress(chainID walletCommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
cursor := ""
ownersMap := make(map[common.Address][]CollectibleOwner)
@ -75,8 +79,10 @@ func (o *Client) FetchCollectibleOwnersByContractAddress(chainID walletCommon.Ch
resp, err := o.doQuery(url)
if err != nil {
o.connectionStatus.SetIsConnected(false)
return nil, err
}
o.connectionStatus.SetIsConnected(true)
defer resp.Body.Close()
@ -141,8 +147,10 @@ func (o *Client) fetchOwnedAssets(chainID walletCommon.ChainID, owner common.Add
resp, err := o.doQuery(url)
if err != nil {
o.connectionStatus.SetIsConnected(false)
return nil, err
}
o.connectionStatus.SetIsConnected(true)
defer resp.Body.Close()
@ -187,8 +195,10 @@ func (o *Client) FetchAssetsByCollectibleUniqueID(uniqueIDs []thirdparty.Collect
resp, err := o.doQuery(url)
if err != nil {
o.connectionStatus.SetIsConnected(false)
return nil, err
}
o.connectionStatus.SetIsConnected(true)
defer resp.Body.Close()
@ -224,8 +234,10 @@ func (o *Client) FetchCollectionsDataByContractID(contractIDs []thirdparty.Contr
resp, err := o.doQuery(url)
if err != nil {
o.connectionStatus.SetIsConnected(false)
return nil, err
}
o.connectionStatus.SetIsConnected(true)
defer resp.Body.Close()

View File

@ -8,16 +8,10 @@ import (
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/connection"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/walletevent"
)
const (
EventOpenseaV1StatusChanged walletevent.EventType = "wallet-collectible-opensea-v1-status-changed"
)
const AssetLimit = 200
@ -46,6 +40,10 @@ func (o *Client) IsChainSupported(chainID walletCommon.ChainID) bool {
return err == nil
}
func (o *Client) IsConnected() bool {
return o.connectionStatus.IsConnected()
}
func getURL(chainID walletCommon.ChainID, path string) (string, error) {
baseURL, err := getBaseURL(chainID)
if err != nil {
@ -63,11 +61,11 @@ type Client struct {
}
// new opensea v1 client.
func NewClient(apiKey string, httpClient *HTTPClient, feed *event.Feed) *Client {
func NewClient(apiKey string, httpClient *HTTPClient) *Client {
return &Client{
client: httpClient,
apiKey: apiKey,
connectionStatus: connection.NewStatus(EventOpenseaV1StatusChanged, feed),
connectionStatus: connection.NewStatus(),
urlGetter: getURL,
}
}

View File

@ -28,7 +28,7 @@ func initTestClient(srv *httptest.Server) *Client {
return srv.URL, nil
}
status := connection.NewStatus("", nil)
status := connection.NewStatus()
client := &HTTPClient{
client: srv.Client(),

View File

@ -8,16 +8,10 @@ import (
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/connection"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/walletevent"
)
const (
EventOpenseaV2StatusChanged walletevent.EventType = "wallet-collectible-opensea-v2-status-changed"
)
const assetLimitV2 = 50
@ -42,6 +36,10 @@ func (o *ClientV2) IsChainSupported(chainID walletCommon.ChainID) bool {
return err == nil
}
func (o *ClientV2) IsConnected() bool {
return o.connectionStatus.IsConnected()
}
func getV2URL(chainID walletCommon.ChainID, path string) (string, error) {
baseURL, err := getV2BaseURL(chainID)
if err != nil {
@ -59,11 +57,11 @@ type ClientV2 struct {
}
// new opensea v2 client.
func NewClientV2(apiKey string, httpClient *HTTPClient, feed *event.Feed) *ClientV2 {
func NewClientV2(apiKey string, httpClient *HTTPClient) *ClientV2 {
return &ClientV2{
client: httpClient,
apiKey: apiKey,
connectionStatus: connection.NewStatus(EventOpenseaV2StatusChanged, feed),
connectionStatus: connection.NewStatus(),
urlGetter: getV2URL,
}
}