mirror of
https://github.com/status-im/status-go.git
synced 2025-01-12 15:45:07 +00:00
feat: implement collectible connection status
This commit is contained in:
parent
ba1f8ba923
commit
bd6f9b098b
@ -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)
|
||||
}
|
||||
|
@ -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(×tamp)
|
||||
|
||||
if err != nil {
|
||||
return InvalidTimestamp, err
|
||||
}
|
||||
if timestamp.Valid {
|
||||
return timestamp.Int64, nil
|
||||
}
|
||||
|
||||
return InvalidTimestamp, nil
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -190,6 +190,7 @@ func (s *Service) GetCollectiblesDetailsAsync(requestID int32, uniqueIDs []third
|
||||
|
||||
func (s *Service) RefetchOwnedCollectibles() {
|
||||
s.stopPeriodicalOwnershipFetch()
|
||||
s.manager.ResetConnectionStatus()
|
||||
_ = s.startPeriodicalOwnershipFetch()
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
60
services/wallet/connection/status_notifier.go
Normal file
60
services/wallet/connection/status_notifier.go
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
25
services/wallet/connection/types.go
Normal file
25
services/wallet/connection/types.go
Normal 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,
|
||||
}
|
||||
}
|
@ -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, ¶ms.NodeConfig{}, nil, nil, nil, nil)
|
||||
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
||||
require.NoError(t, err)
|
||||
|
||||
service := NewService(db, nil, &rpc.Client{}, nil, nil, nil, ¶ms.NodeConfig{}, nil, nil, nil, nil)
|
||||
|
||||
data, err := service.KeycardPairings().GetPairingsJSONFileContent()
|
||||
require.NoError(t, err)
|
||||
|
@ -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)
|
||||
|
23
services/wallet/thirdparty/alchemy/client.go
vendored
23
services/wallet/thirdparty/alchemy/client.go
vendored
@ -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()
|
||||
|
||||
|
@ -20,6 +20,7 @@ const FetchFromStartCursor = ""
|
||||
type CollectibleProvider interface {
|
||||
ID() string
|
||||
IsChainSupported(chainID w_common.ChainID) bool
|
||||
IsConnected() bool
|
||||
}
|
||||
|
||||
type ContractID struct {
|
||||
|
28
services/wallet/thirdparty/infura/client.go
vendored
28
services/wallet/thirdparty/infura/client.go
vendored
@ -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()
|
||||
|
||||
|
14
services/wallet/thirdparty/opensea/client.go
vendored
14
services/wallet/thirdparty/opensea/client.go
vendored
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
14
services/wallet/thirdparty/opensea/client_v2.go
vendored
14
services/wallet/thirdparty/opensea/client_v2.go
vendored
@ -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,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user