feat(wallet): add community info to collectibles

This commit is contained in:
Dario Gabriel Lipicar 2023-09-21 09:40:58 -03:00 committed by dlipicar
parent 2b53d71708
commit ba5cd9c1a4
15 changed files with 573 additions and 161 deletions

View File

@ -122,6 +122,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
services = append(services, wakuext) services = append(services, wakuext)
b.SetWalletCollectibleMetadataProvider(wakuext) b.SetWalletCollectibleMetadataProvider(wakuext)
b.SetWalletCollectibleCommunityInfoProvider(wakuext)
} }
if config.WakuV2Config.Enabled { if config.WakuV2Config.Enabled {
@ -149,6 +150,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
services = append(services, wakuext) services = append(services, wakuext)
b.SetWalletCollectibleMetadataProvider(wakuext) b.SetWalletCollectibleMetadataProvider(wakuext)
b.SetWalletCollectibleCommunityInfoProvider(wakuext)
} }
// We ignore for now local notifications flag as users who are upgrading have no mean to enable it // We ignore for now local notifications flag as users who are upgrading have no mean to enable it
@ -503,6 +505,12 @@ func (b *StatusNode) SetWalletCollectibleMetadataProvider(provider thirdparty.Co
} }
} }
func (b *StatusNode) SetWalletCollectibleCommunityInfoProvider(provider thirdparty.CollectibleCommunityInfoProvider) {
if b.walletSrvc != nil {
b.walletSrvc.SetCollectibleCommunityInfoProvider(provider)
}
}
func (b *StatusNode) walletService(accountsDB *accounts.Database, accountsFeed *event.Feed, walletFeed *event.Feed) *wallet.Service { func (b *StatusNode) walletService(accountsDB *accounts.Database, accountsFeed *event.Feed, walletFeed *event.Feed) *wallet.Service {
if b.walletSrvc == nil { if b.walletSrvc == nil {
b.walletSrvc = wallet.NewService( b.walletSrvc = wallet.NewService(

View File

@ -4601,55 +4601,64 @@ func (m *Manager) HandleCommunityTokensMetadata(community *Community) error {
return err return err
} }
if !exists { if !exists {
communityToken := &community_token.CommunityToken{ // Fetch community token to make sure it's stored in the DB, discard result
CommunityID: communityID, communityToken, err := m.FetchCommunityToken(community, tokenMetadata, chainID, address)
Address: address,
TokenType: tokenMetadata.TokenType,
Name: tokenMetadata.Name,
Symbol: tokenMetadata.Symbol,
Description: tokenMetadata.Description,
Transferable: true,
RemoteSelfDestruct: false,
ChainID: int(chainID),
DeployState: community_token.Deployed,
Base64Image: tokenMetadata.Image,
Decimals: int(tokenMetadata.Decimals),
}
switch tokenMetadata.TokenType {
case protobuf.CommunityTokenType_ERC721:
contractData, err := m.communityTokensService.GetCollectibleContractData(chainID, address)
if err != nil {
return err
}
communityToken.Supply = contractData.TotalSupply
communityToken.Transferable = contractData.Transferable
communityToken.RemoteSelfDestruct = contractData.RemoteBurnable
communityToken.InfiniteSupply = contractData.InfiniteSupply
case protobuf.CommunityTokenType_ERC20:
contractData, err := m.communityTokensService.GetAssetContractData(chainID, address)
if err != nil {
return err
}
communityToken.Supply = contractData.TotalSupply
communityToken.InfiniteSupply = contractData.InfiniteSupply
}
communityToken.PrivilegesLevel = getPrivilegesLevel(chainID, address, community.TokenPermissions())
err = m.persistence.AddCommunityToken(communityToken)
if err != nil { if err != nil {
return err return err
} }
return m.persistence.AddCommunityToken(communityToken)
} }
} }
} }
return nil return nil
} }
func (m *Manager) FetchCommunityToken(community *Community, tokenMetadata *protobuf.CommunityTokenMetadata, chainID uint64, contractAddress string) (*community_token.CommunityToken, error) {
communityID := community.IDString()
communityToken := &community_token.CommunityToken{
CommunityID: communityID,
Address: contractAddress,
TokenType: tokenMetadata.TokenType,
Name: tokenMetadata.Name,
Symbol: tokenMetadata.Symbol,
Description: tokenMetadata.Description,
Transferable: true,
RemoteSelfDestruct: false,
ChainID: int(chainID),
DeployState: community_token.Deployed,
Base64Image: tokenMetadata.Image,
Decimals: int(tokenMetadata.Decimals),
}
switch tokenMetadata.TokenType {
case protobuf.CommunityTokenType_ERC721:
contractData, err := m.communityTokensService.GetCollectibleContractData(chainID, contractAddress)
if err != nil {
return nil, err
}
communityToken.Supply = contractData.TotalSupply
communityToken.Transferable = contractData.Transferable
communityToken.RemoteSelfDestruct = contractData.RemoteBurnable
communityToken.InfiniteSupply = contractData.InfiniteSupply
case protobuf.CommunityTokenType_ERC20:
contractData, err := m.communityTokensService.GetAssetContractData(chainID, contractAddress)
if err != nil {
return nil, err
}
communityToken.Supply = contractData.TotalSupply
communityToken.InfiniteSupply = contractData.InfiniteSupply
}
communityToken.PrivilegesLevel = getPrivilegesLevel(chainID, contractAddress, community.TokenPermissions())
return communityToken, nil
}
func getPrivilegesLevel(chainID uint64, tokenAddress string, tokenPermissions map[string]*CommunityTokenPermission) community_token.PrivilegesLevel { func getPrivilegesLevel(chainID uint64, tokenAddress string, tokenPermissions map[string]*CommunityTokenPermission) community_token.PrivilegesLevel {
for _, permission := range tokenPermissions { for _, permission := range tokenPermissions {
if permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER || permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER { if permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER || permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER {

View File

@ -4492,6 +4492,10 @@ func (m *Messenger) chatMessagesToWakuMessages(chatMessages []*common.Message, c
return wakuMessages, nil return wakuMessages, nil
} }
func (m *Messenger) GetCommunityToken(communityID string, chainID int, address string) (*token.CommunityToken, error) {
return m.communitiesManager.GetCommunityToken(communityID, chainID, address)
}
func (m *Messenger) GetCommunityTokens(communityID string) ([]*token.CommunityToken, error) { func (m *Messenger) GetCommunityTokens(communityID string) ([]*token.CommunityToken, error) {
return m.communitiesManager.GetCommunityTokens(communityID) return m.communitiesManager.GetCommunityTokens(communityID)
} }

View File

@ -32,12 +32,16 @@ import (
coretypes "github.com/status-im/status-go/eth-node/core/types" coretypes "github.com/status-im/status-go/eth-node/core/types"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts" "github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol" "github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/protocol/anonmetrics" "github.com/status-im/status-go/protocol/anonmetrics"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/communities/token"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/pushnotificationclient" "github.com/status-im/status-go/protocol/pushnotificationclient"
"github.com/status-im/status-go/protocol/pushnotificationserver" "github.com/status-im/status-go/protocol/pushnotificationserver"
"github.com/status-im/status-go/protocol/transport" "github.com/status-im/status-go/protocol/transport"
@ -53,6 +57,8 @@ import (
"github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/thirdparty"
) )
const infinityString = "∞"
// EnvelopeEventsHandler used for two different event types. // EnvelopeEventsHandler used for two different event types.
type EnvelopeEventsHandler interface { type EnvelopeEventsHandler interface {
EnvelopeSent([][]byte) EnvelopeSent([][]byte)
@ -63,7 +69,6 @@ type EnvelopeEventsHandler interface {
// Service is a service that provides some additional API to whisper-based protocols like Whisper or Waku. // Service is a service that provides some additional API to whisper-based protocols like Whisper or Waku.
type Service struct { type Service struct {
thirdparty.CollectibleMetadataProvider
messenger *protocol.Messenger messenger *protocol.Messenger
identity *ecdsa.PrivateKey identity *ecdsa.PrivateKey
cancelMessenger chan struct{} cancelMessenger chan struct{}
@ -556,16 +561,114 @@ func (s *Service) CanProvideCollectibleMetadata(id thirdparty.CollectibleUniqueI
} }
func (s *Service) FetchCollectibleMetadata(id thirdparty.CollectibleUniqueID, tokenURI string) (*thirdparty.FullCollectibleData, error) { func (s *Service) FetchCollectibleMetadata(id thirdparty.CollectibleUniqueID, tokenURI string) (*thirdparty.FullCollectibleData, error) {
if s.messenger == nil {
return nil, fmt.Errorf("messenger not ready")
}
communityID := tokenURIToCommunityID(tokenURI) communityID := tokenURIToCommunityID(tokenURI)
if communityID == "" { if communityID == "" {
return nil, fmt.Errorf("invalid tokenURI") return nil, fmt.Errorf("invalid tokenURI")
} }
community, err := s.fetchCommunity(communityID)
if err != nil {
return nil, err
}
if community == nil {
return nil, nil
}
tokenMetadata, err := s.fetchCommunityCollectibleMetadata(community, id.ContractID)
if err != nil {
return nil, err
}
if tokenMetadata == nil {
return nil, nil
}
token, err := s.fetchCommunityToken(communityID, id.ContractID)
if err != nil {
return nil, err
}
return &thirdparty.FullCollectibleData{
CollectibleData: thirdparty.CollectibleData{
ID: id,
CommunityID: communityID,
Name: tokenMetadata.GetName(),
Description: tokenMetadata.GetDescription(),
ImageURL: tokenMetadata.GetImage(),
TokenURI: tokenURI,
Traits: getCollectibleCommunityTraits(token),
},
CollectionData: &thirdparty.CollectionData{
ID: id.ContractID,
CommunityID: communityID,
Name: tokenMetadata.GetName(),
ImageURL: tokenMetadata.GetImage(),
},
}, nil
}
func permissionTypeToPrivilegesLevel(permissionType protobuf.CommunityTokenPermission_Type) token.PrivilegesLevel {
switch permissionType {
case protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER:
return token.OwnerLevel
case protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER:
return token.MasterLevel
default:
return token.CommunityLevel
}
}
func (s *Service) FetchCollectibleCommunityInfo(communityID string, id thirdparty.CollectibleUniqueID) (*thirdparty.CollectiblesCommunityInfo, error) {
community, err := s.fetchCommunity(communityID)
if err != nil {
return nil, err
}
if community == nil {
return nil, nil
}
metadata, err := s.fetchCommunityCollectibleMetadata(community, id.ContractID)
if err != nil {
return nil, err
}
if metadata == nil {
return nil, nil
}
permission := fetchCommunityCollectiblePermission(community, id)
privilegesLevel := token.CommunityLevel
if permission != nil {
privilegesLevel = permissionTypeToPrivilegesLevel(permission.GetType())
}
return &thirdparty.CollectiblesCommunityInfo{
CommunityID: communityID,
CommunityName: community.Name(),
CommunityColor: community.Color(),
CommunityImage: fetchCommunityImage(community),
PrivilegesLevel: privilegesLevel,
}, nil
}
func (s *Service) FetchCollectibleCommunityTraits(communityID string, id thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleTrait, error) {
token, err := s.fetchCommunityToken(communityID, id.ContractID)
if err != nil {
return nil, err
}
return getCollectibleCommunityTraits(token), nil
}
func (s *Service) fetchCommunity(communityID string) (*communities.Community, error) {
if s.messenger == nil {
return nil, fmt.Errorf("messenger not ready")
}
// Try to fetch metadata from Messenger communities // Try to fetch metadata from Messenger communities
community, err := s.messenger.RequestCommunityInfoFromMailserver(communityID, true) community, err := s.messenger.RequestCommunityInfoFromMailserver(communityID, true)
@ -573,29 +676,155 @@ func (s *Service) FetchCollectibleMetadata(id thirdparty.CollectibleUniqueID, to
return nil, err return nil, err
} }
if community != nil { return community, nil
tokensMetadata := community.CommunityTokensMetadata() }
for _, tokenMetadata := range tokensMetadata { func (s *Service) fetchCommunityToken(communityID string, contractID thirdparty.ContractID) (*token.CommunityToken, error) {
contractAddresses := tokenMetadata.GetContractAddresses() if s.messenger == nil {
if contractAddresses[uint64(id.ContractID.ChainID)] == id.ContractID.Address.Hex() { return nil, fmt.Errorf("messenger not ready")
return &thirdparty.FullCollectibleData{ }
CollectibleData: thirdparty.CollectibleData{
ID: id, return s.messenger.GetCommunityToken(communityID, int(contractID.ChainID), contractID.Address.String())
Name: tokenMetadata.GetName(), }
Description: tokenMetadata.GetDescription(),
ImageURL: tokenMetadata.GetImage(), func (s *Service) fetchCommunityCollectibleMetadata(community *communities.Community, contractID thirdparty.ContractID) (*protobuf.CommunityTokenMetadata, error) {
TokenURI: tokenURI, tokensMetadata := community.CommunityTokensMetadata()
},
CollectionData: &thirdparty.CollectionData{ for _, tokenMetadata := range tokensMetadata {
ID: id.ContractID, contractAddresses := tokenMetadata.GetContractAddresses()
Name: tokenMetadata.GetName(), if contractAddresses[uint64(contractID.ChainID)] == contractID.Address.Hex() {
ImageURL: tokenMetadata.GetImage(), return tokenMetadata, nil
},
}, nil
}
} }
} }
return nil, nil return nil, nil
} }
func tokenCriterionContainsCollectible(tokenCriterion *protobuf.TokenCriteria, id thirdparty.CollectibleUniqueID) bool {
// Check if token type matches
if tokenCriterion.Type != protobuf.CommunityTokenType_ERC721 {
return false
}
for chainID, contractAddressStr := range tokenCriterion.ContractAddresses {
if chainID != uint64(id.ContractID.ChainID) {
continue
}
contractAddress := commongethtypes.HexToAddress(contractAddressStr)
if contractAddress != id.ContractID.Address {
continue
}
if len(tokenCriterion.TokenIds) == 0 {
return true
}
for _, tokenID := range tokenCriterion.TokenIds {
tokenIDBigInt := new(big.Int).SetUint64(tokenID)
if id.TokenID.Cmp(tokenIDBigInt) == 0 {
return true
}
}
}
return false
}
func permissionContainsCollectible(permission *communities.CommunityTokenPermission, id thirdparty.CollectibleUniqueID) bool {
// See if any token criterion contains the collectible we're looking for
for _, tokenCriterion := range permission.TokenCriteria {
if tokenCriterionContainsCollectible(tokenCriterion, id) {
return true
}
}
return false
}
func fetchCommunityCollectiblePermission(community *communities.Community, id thirdparty.CollectibleUniqueID) *communities.CommunityTokenPermission {
// Permnission types of interest
permissionTypes := []protobuf.CommunityTokenPermission_Type{
protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER,
protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER,
}
for _, permissionType := range permissionTypes {
permissions := community.TokenPermissionsByType(permissionType)
// See if any community permission matches the type we're looking for
for _, permission := range permissions {
if permissionContainsCollectible(permission, id) {
return permission
}
}
}
return nil
}
func fetchCommunityImage(community *communities.Community) string {
imageTypes := []string{
images.LargeDimName,
images.SmallDimName,
}
communityImages := community.Images()
for _, imageType := range imageTypes {
if pbImage, ok := communityImages[imageType]; ok {
imageBase64, err := images.GetPayloadDataURI(pbImage.Payload)
if err == nil {
return imageBase64
}
}
}
return ""
}
func boolToString(value bool) string {
if value {
return "Yes"
}
return "No"
}
func getCollectibleCommunityTraits(token *token.CommunityToken) []thirdparty.CollectibleTrait {
if token == nil {
return make([]thirdparty.CollectibleTrait, 0)
}
totalStr := infinityString
availableStr := infinityString
if !token.InfiniteSupply {
totalStr = token.Supply.String()
// TODO: calculate available supply. See services/communitytokens/api.go
availableStr = totalStr
}
transferableStr := boolToString(token.Transferable)
destructibleStr := boolToString(token.RemoteSelfDestruct)
return []thirdparty.CollectibleTrait{
{
TraitType: "Symbol",
Value: token.Symbol,
},
{
TraitType: "Total",
Value: totalStr,
},
{
TraitType: "Available",
Value: availableStr,
},
{
TraitType: "Transferable",
Value: transferableStr,
},
{
TraitType: "Destructible",
Value: destructibleStr,
},
}
}

View File

@ -20,7 +20,7 @@ func NewCollectibleDataDB(sqlDb *sql.DB) *CollectibleDataDB {
} }
} }
const collectibleDataColumns = "chain_id, contract_address, token_id, provider, name, description, permalink, image_url, animation_url, animation_media_type, background_color, token_uri" const collectibleDataColumns = "chain_id, contract_address, token_id, provider, name, description, permalink, image_url, animation_url, animation_media_type, background_color, token_uri, community_id"
const collectibleTraitsColumns = "chain_id, contract_address, token_id, trait_type, trait_value, display_type, max_value" const collectibleTraitsColumns = "chain_id, contract_address, token_id, trait_type, trait_value, display_type, max_value"
const selectCollectibleTraitsColumns = "trait_type, trait_value, display_type, max_value" const selectCollectibleTraitsColumns = "trait_type, trait_value, display_type, max_value"
@ -106,7 +106,7 @@ func upsertCollectibleTraits(creator sqlite.StatementCreator, id thirdparty.Coll
func upsertCollectiblesData(creator sqlite.StatementCreator, collectibles []thirdparty.CollectibleData) error { func upsertCollectiblesData(creator sqlite.StatementCreator, collectibles []thirdparty.CollectibleData) error {
insertCollectible, err := creator.Prepare(fmt.Sprintf(`INSERT OR REPLACE INTO collectible_data_cache (%s) insertCollectible, err := creator.Prepare(fmt.Sprintf(`INSERT OR REPLACE INTO collectible_data_cache (%s)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, collectibleDataColumns)) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, collectibleDataColumns))
if err != nil { if err != nil {
return err return err
} }
@ -125,6 +125,7 @@ func upsertCollectiblesData(creator sqlite.StatementCreator, collectibles []thir
c.AnimationMediaType, c.AnimationMediaType,
c.BackgroundColor, c.BackgroundColor,
c.TokenURI, c.TokenURI,
c.CommunityID,
) )
if err != nil { if err != nil {
return err return err
@ -181,6 +182,7 @@ func scanCollectiblesDataRow(row *sql.Row) (*thirdparty.CollectibleData, error)
&c.AnimationMediaType, &c.AnimationMediaType,
&c.BackgroundColor, &c.BackgroundColor,
&c.TokenURI, &c.TokenURI,
&c.CommunityID,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -7,16 +7,17 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
w_common "github.com/status-im/status-go/services/wallet/common" w_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func setupCollectibleDataDBTest(t *testing.T) (*CollectibleDataDB, func()) { func setupCollectibleDataDBTest(t *testing.T) (*CollectibleDataDB, func()) {
db, err := appdatabase.InitializeDB(":memory:", "wallet-collectibles-data-db-tests", 1) db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err) require.NoError(t, err)
return NewCollectibleDataDB(db), func() { return NewCollectibleDataDB(db), func() {
require.NoError(t, db.Close()) require.NoError(t, db.Close())
@ -64,6 +65,7 @@ func generateTestCollectiblesData(count int) (result []thirdparty.CollectibleDat
}, },
BackgroundColor: fmt.Sprintf("backgroundcolor-%d", i), BackgroundColor: fmt.Sprintf("backgroundcolor-%d", i),
TokenURI: fmt.Sprintf("tokenuri-%d", i), TokenURI: fmt.Sprintf("tokenuri-%d", i),
CommunityID: fmt.Sprintf("communityid-%d", i),
} }
result = append(result, newCollectible) result = append(result, newCollectible)
} }

View File

@ -18,7 +18,7 @@ func NewCollectionDataDB(sqlDb *sql.DB) *CollectionDataDB {
} }
} }
const collectionDataColumns = "chain_id, contract_address, provider, name, slug, image_url" const collectionDataColumns = "chain_id, contract_address, provider, name, slug, image_url, community_id"
const collectionTraitsColumns = "chain_id, contract_address, trait_type, min, max" const collectionTraitsColumns = "chain_id, contract_address, trait_type, min, max"
const selectCollectionTraitsColumns = "trait_type, min, max" const selectCollectionTraitsColumns = "trait_type, min, max"
@ -100,7 +100,7 @@ func upsertCollectionTraits(creator sqlite.StatementCreator, id thirdparty.Contr
func upsertCollectionsData(creator sqlite.StatementCreator, collections []thirdparty.CollectionData) error { func upsertCollectionsData(creator sqlite.StatementCreator, collections []thirdparty.CollectionData) error {
insertCollection, err := creator.Prepare(fmt.Sprintf(`INSERT OR REPLACE INTO collection_data_cache (%s) insertCollection, err := creator.Prepare(fmt.Sprintf(`INSERT OR REPLACE INTO collection_data_cache (%s)
VALUES (?, ?, ?, ?, ?, ?)`, collectionDataColumns)) VALUES (?, ?, ?, ?, ?, ?, ?)`, collectionDataColumns))
if err != nil { if err != nil {
return err return err
} }
@ -113,6 +113,7 @@ func upsertCollectionsData(creator sqlite.StatementCreator, collections []thirdp
c.Name, c.Name,
c.Slug, c.Slug,
c.ImageURL, c.ImageURL,
c.CommunityID,
) )
if err != nil { if err != nil {
return err return err
@ -160,6 +161,7 @@ func scanCollectionsDataRow(row *sql.Row) (*thirdparty.CollectionData, error) {
&c.Name, &c.Name,
&c.Slug, &c.Slug,
&c.ImageURL, &c.ImageURL,
&c.CommunityID,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -7,15 +7,16 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/appdatabase"
w_common "github.com/status-im/status-go/services/wallet/common" w_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func setupCollectionDataDBTest(t *testing.T) (*CollectionDataDB, func()) { func setupCollectionDataDBTest(t *testing.T) (*CollectionDataDB, func()) {
db, err := appdatabase.InitializeDB(":memory:", "wallet-collections-data-db-tests", 1) db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err) require.NoError(t, err)
return NewCollectionDataDB(db), func() { return NewCollectionDataDB(db), func() {
require.NoError(t, db.Close()) require.NoError(t, db.Close())
@ -39,11 +40,12 @@ func generateTestCollectionsData(count int) (result []thirdparty.CollectionData)
ChainID: w_common.ChainID(i), ChainID: w_common.ChainID(i),
Address: common.BigToAddress(bigI), Address: common.BigToAddress(bigI),
}, },
Provider: fmt.Sprintf("provider-%d", i), Provider: fmt.Sprintf("provider-%d", i),
Name: fmt.Sprintf("name-%d", i), Name: fmt.Sprintf("name-%d", i),
Slug: fmt.Sprintf("slug-%d", i), Slug: fmt.Sprintf("slug-%d", i),
ImageURL: fmt.Sprintf("imageurl-%d", i), ImageURL: fmt.Sprintf("imageurl-%d", i),
Traits: traits, Traits: traits,
CommunityID: fmt.Sprintf("community-%d", i),
} }
result = append(result, newCollection) result = append(result, newCollection)
} }

View File

@ -49,13 +49,21 @@ type Manager struct {
collectibleDataProviders []thirdparty.CollectibleDataProvider collectibleDataProviders []thirdparty.CollectibleDataProvider
collectionDataProviders []thirdparty.CollectionDataProvider collectionDataProviders []thirdparty.CollectionDataProvider
metadataProvider thirdparty.CollectibleMetadataProvider metadataProvider thirdparty.CollectibleMetadataProvider
communityInfoProvider thirdparty.CollectibleCommunityInfoProvider
opensea *opensea.Client opensea *opensea.Client
httpClient *http.Client httpClient *http.Client
collectiblesDataDB *CollectibleDataDB collectiblesDataDB *CollectibleDataDB
collectionsDataDB *CollectionDataDB collectionsDataDB *CollectionDataDB
} }
func NewManager(db *sql.DB, rpcClient *rpc.Client, contractOwnershipProviders []thirdparty.CollectibleContractOwnershipProvider, accountOwnershipProviders []thirdparty.CollectibleAccountOwnershipProvider, collectibleDataProviders []thirdparty.CollectibleDataProvider, collectionDataProviders []thirdparty.CollectionDataProvider, opensea *opensea.Client) *Manager { func NewManager(
db *sql.DB,
rpcClient *rpc.Client,
contractOwnershipProviders []thirdparty.CollectibleContractOwnershipProvider,
accountOwnershipProviders []thirdparty.CollectibleAccountOwnershipProvider,
collectibleDataProviders []thirdparty.CollectibleDataProvider,
collectionDataProviders []thirdparty.CollectionDataProvider,
opensea *opensea.Client) *Manager {
hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{ hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{
Timeout: 10000, Timeout: 10000,
MaxConcurrentRequests: 100, MaxConcurrentRequests: 100,
@ -139,6 +147,10 @@ func (o *Manager) SetMetadataProvider(metadataProvider thirdparty.CollectibleMet
o.metadataProvider = metadataProvider o.metadataProvider = metadataProvider
} }
func (o *Manager) SetCommunityInfoProvider(communityInfoProvider thirdparty.CollectibleCommunityInfoProvider) {
o.communityInfoProvider = communityInfoProvider
}
func (o *Manager) FetchAllCollectionsByOwner(chainID walletCommon.ChainID, owner common.Address) ([]opensea.OwnedCollection, error) { func (o *Manager) FetchAllCollectionsByOwner(chainID walletCommon.ChainID, owner common.Address) ([]opensea.OwnedCollection, error) {
return o.opensea.FetchAllCollectionsByOwner(chainID, owner) return o.opensea.FetchAllCollectionsByOwner(chainID, owner)
} }
@ -375,8 +387,7 @@ func (o *Manager) FetchCollectibleOwnersByContractAddress(chainID walletCommon.C
func isMetadataEmpty(asset thirdparty.CollectibleData) bool { func isMetadataEmpty(asset thirdparty.CollectibleData) bool {
return asset.Name == "" && return asset.Name == "" &&
asset.Description == "" && asset.Description == "" &&
asset.ImageURL == "" && asset.ImageURL == ""
asset.TokenURI == ""
} }
func (o *Manager) fetchTokenURI(id thirdparty.CollectibleUniqueID) (string, error) { func (o *Manager) fetchTokenURI(id thirdparty.CollectibleUniqueID) (string, error) {
@ -426,14 +437,20 @@ func (o *Manager) processFullCollectibleData(assets []thirdparty.FullCollectible
if o.metadataProvider == nil { if o.metadataProvider == nil {
return fmt.Errorf("CollectibleMetadataProvider not available") return fmt.Errorf("CollectibleMetadataProvider not available")
} }
tokenURI, err := o.fetchTokenURI(id)
if err != nil { tokenURI := asset.CollectibleData.TokenURI
return err var err error
if tokenURI == "" {
tokenURI, err = o.fetchTokenURI(id)
if err != nil {
return err
}
asset.CollectibleData.TokenURI = tokenURI
} }
asset.CollectibleData.TokenURI = tokenURI
canProvide, err := o.metadataProvider.CanProvideCollectibleMetadata(id, tokenURI) canProvide, err := o.metadataProvider.CanProvideCollectibleMetadata(id, tokenURI)
if err != nil { if err != nil {
@ -538,3 +555,40 @@ func (o *Manager) getCacheFullCollectibleData(uniqueIDs []thirdparty.Collectible
return ret, nil return ret, nil
} }
func (o *Manager) FetchCollectibleCommunityInfo(communityID string, id thirdparty.CollectibleUniqueID) (*thirdparty.CollectiblesCommunityInfo, error) {
if o.communityInfoProvider == nil {
return nil, fmt.Errorf("CollectibleCommunityInfoProvider not available")
}
return o.communityInfoProvider.FetchCollectibleCommunityInfo(communityID, id)
}
func (o *Manager) FetchCollectibleCommunityTraits(communityID string, id thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleTrait, error) {
if o.communityInfoProvider == nil {
return nil, fmt.Errorf("CollectibleCommunityInfoProvider not available")
}
traits, err := o.communityInfoProvider.FetchCollectibleCommunityTraits(communityID, id)
if err != nil {
return nil, err
}
collectibleIDs := []thirdparty.CollectibleUniqueID{id}
collectiblesData, err := o.collectiblesDataDB.GetData(collectibleIDs)
if err != nil {
return nil, err
}
if collectible, ok := collectiblesData[id.HashKey()]; ok {
collectible.Traits = traits
collectiblesData[id.HashKey()] = collectible
err = o.collectiblesDataDB.SetData(mapToList(collectiblesData))
if err != nil {
return nil, err
}
}
return traits, nil
}

View File

@ -116,7 +116,7 @@ type GetCollectiblesDetailsResponse struct {
} }
type filterOwnedCollectiblesTaskReturnType struct { type filterOwnedCollectiblesTaskReturnType struct {
collectibles []CollectibleHeader headers []CollectibleHeader
hasMore bool hasMore bool
ownershipStatus OwnershipStatusPerAddressAndChainID ownershipStatus OwnershipStatusPerAddressAndChainID
} }
@ -138,9 +138,10 @@ func (s *Service) FilterOwnedCollectiblesAsync(requestID int32, chainIDs []walle
if err != nil { if err != nil {
return nil, err return nil, err
} }
headers, err := s.fullCollectiblesDataToHeaders(data)
return filterOwnedCollectiblesTaskReturnType{ return filterOwnedCollectiblesTaskReturnType{
collectibles: fullCollectiblesDataToHeaders(data), headers: headers,
hasMore: hasMore, hasMore: hasMore,
ownershipStatus: ownershipStatus, ownershipStatus: ownershipStatus,
}, err }, err
@ -153,7 +154,7 @@ func (s *Service) FilterOwnedCollectiblesAsync(requestID int32, chainIDs []walle
res.ErrorCode = ErrorCodeTaskCanceled res.ErrorCode = ErrorCodeTaskCanceled
} else if err == nil { } else if err == nil {
fnRet := result.(filterOwnedCollectiblesTaskReturnType) fnRet := result.(filterOwnedCollectiblesTaskReturnType)
res.Collectibles = fnRet.collectibles res.Collectibles = fnRet.headers
res.Offset = offset res.Offset = offset
res.HasMore = fnRet.hasMore res.HasMore = fnRet.hasMore
res.OwnershipStatus = fnRet.ownershipStatus res.OwnershipStatus = fnRet.ownershipStatus
@ -167,7 +168,10 @@ func (s *Service) FilterOwnedCollectiblesAsync(requestID int32, chainIDs []walle
func (s *Service) GetCollectiblesDetailsAsync(requestID int32, uniqueIDs []thirdparty.CollectibleUniqueID) { func (s *Service) GetCollectiblesDetailsAsync(requestID int32, uniqueIDs []thirdparty.CollectibleUniqueID) {
s.scheduler.Enqueue(requestID, getCollectiblesDataTask, func(ctx context.Context) (interface{}, error) { s.scheduler.Enqueue(requestID, getCollectiblesDataTask, func(ctx context.Context) (interface{}, error) {
collectibles, err := s.manager.FetchAssetsByCollectibleUniqueID(uniqueIDs) collectibles, err := s.manager.FetchAssetsByCollectibleUniqueID(uniqueIDs)
return collectibles, err if err != nil {
return nil, err
}
return s.fullCollectiblesDataToDetails(collectibles)
}, func(result interface{}, taskType async.TaskType, err error) { }, func(result interface{}, taskType async.TaskType, err error) {
res := GetCollectiblesDetailsResponse{ res := GetCollectiblesDetailsResponse{
ErrorCode: ErrorCodeFailed, ErrorCode: ErrorCodeFailed,
@ -176,8 +180,7 @@ func (s *Service) GetCollectiblesDetailsAsync(requestID int32, uniqueIDs []third
if errors.Is(err, context.Canceled) || errors.Is(err, async.ErrTaskOverwritten) { if errors.Is(err, context.Canceled) || errors.Is(err, async.ErrTaskOverwritten) {
res.ErrorCode = ErrorCodeTaskCanceled res.ErrorCode = ErrorCodeTaskCanceled
} else if err == nil { } else if err == nil {
collectibles := result.([]thirdparty.FullCollectibleData) res.Collectibles = result.([]CollectibleDetails)
res.Collectibles = fullCollectiblesDataToDetails(collectibles)
res.ErrorCode = ErrorCodeSuccess res.ErrorCode = ErrorCodeSuccess
} }
@ -402,3 +405,56 @@ func (s *Service) GetOwnershipStatus(chainIDs []walletCommon.ChainID, owners []c
return ret, nil return ret, nil
} }
func (s *Service) fullCollectiblesDataToHeaders(data []thirdparty.FullCollectibleData) ([]CollectibleHeader, error) {
res := make([]CollectibleHeader, 0, len(data))
for _, c := range data {
header := fullCollectibleDataToHeader(c)
if c.CollectibleData.CommunityID != "" {
communityInfo, err := s.manager.FetchCollectibleCommunityInfo(c.CollectibleData.CommunityID, c.CollectibleData.ID)
if err != nil {
return nil, err
}
header.CommunityHeader = &CommunityHeader{
CommunityID: communityInfo.CommunityID,
CommunityName: communityInfo.CommunityName,
CommunityColor: communityInfo.CommunityColor,
PrivilegesLevel: communityInfo.PrivilegesLevel,
}
}
res = append(res, header)
}
return res, nil
}
func (s *Service) fullCollectiblesDataToDetails(data []thirdparty.FullCollectibleData) ([]CollectibleDetails, error) {
res := make([]CollectibleDetails, 0, len(data))
for _, c := range data {
details := fullCollectibleDataToDetails(c)
if c.CollectibleData.CommunityID != "" {
traits, err := s.manager.FetchCollectibleCommunityTraits(c.CollectibleData.CommunityID, c.CollectibleData.ID)
if err != nil {
return nil, err
}
details.Traits = traits
communityInfo, err := s.manager.FetchCollectibleCommunityInfo(c.CollectibleData.CommunityID, c.CollectibleData.ID)
if err != nil {
return nil, err
}
details.CommunityInfo = communityInfo
}
res = append(res, details)
}
return res, nil
}

View File

@ -1,20 +1,24 @@
package collectibles package collectibles
import "github.com/status-im/status-go/services/wallet/thirdparty" import (
"github.com/status-im/status-go/protocol/communities/token"
"github.com/status-im/status-go/services/wallet/thirdparty"
)
// Combined Collection+Collectible info, used to display a detailed view of a collectible // Combined Collection+Collectible info, used to display a detailed view of a collectible
type CollectibleDetails struct { type CollectibleDetails struct {
ID thirdparty.CollectibleUniqueID `json:"id"` ID thirdparty.CollectibleUniqueID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
ImageURL string `json:"image_url"` ImageURL string `json:"image_url"`
AnimationURL string `json:"animation_url"` AnimationURL string `json:"animation_url"`
AnimationMediaType string `json:"animation_media_type"` AnimationMediaType string `json:"animation_media_type"`
Traits []thirdparty.CollectibleTrait `json:"traits"` Traits []thirdparty.CollectibleTrait `json:"traits"`
BackgroundColor string `json:"background_color"` BackgroundColor string `json:"background_color"`
CollectionName string `json:"collection_name"` CollectionName string `json:"collection_name"`
CollectionSlug string `json:"collection_slug"` CollectionSlug string `json:"collection_slug"`
CollectionImageURL string `json:"collection_image_url"` CollectionImageURL string `json:"collection_image_url"`
CommunityInfo *thirdparty.CollectiblesCommunityInfo `json:"community_info,omitempty"`
} }
// Combined Collection+Collectible info, used to display a basic view of a collectible in a list // Combined Collection+Collectible info, used to display a basic view of a collectible in a list
@ -28,6 +32,14 @@ type CollectibleHeader struct {
CollectionName string `json:"collection_name"` CollectionName string `json:"collection_name"`
CollectionSlug string `json:"collection_slug"` CollectionSlug string `json:"collection_slug"`
CollectionImageURL string `json:"collection_image_url"` CollectionImageURL string `json:"collection_image_url"`
CommunityHeader *CommunityHeader `json:"community_header,omitempty"`
}
type CommunityHeader struct {
CommunityID string `json:"community_id"`
CommunityName string `json:"community_name"`
CommunityColor string `json:"community_color"`
PrivilegesLevel token.PrivilegesLevel `json:"privileges_level"`
} }
func fullCollectibleDataToHeader(c thirdparty.FullCollectibleData) CollectibleHeader { func fullCollectibleDataToHeader(c thirdparty.FullCollectibleData) CollectibleHeader {
@ -47,16 +59,6 @@ func fullCollectibleDataToHeader(c thirdparty.FullCollectibleData) CollectibleHe
return ret return ret
} }
func fullCollectiblesDataToHeaders(data []thirdparty.FullCollectibleData) []CollectibleHeader {
res := make([]CollectibleHeader, 0, len(data))
for _, c := range data {
res = append(res, fullCollectibleDataToHeader(c))
}
return res
}
func fullCollectibleDataToDetails(c thirdparty.FullCollectibleData) CollectibleDetails { func fullCollectibleDataToDetails(c thirdparty.FullCollectibleData) CollectibleDetails {
ret := CollectibleDetails{ ret := CollectibleDetails{
ID: c.CollectibleData.ID, ID: c.CollectibleData.ID,
@ -75,13 +77,3 @@ func fullCollectibleDataToDetails(c thirdparty.FullCollectibleData) CollectibleD
} }
return ret return ret
} }
func fullCollectiblesDataToDetails(data []thirdparty.FullCollectibleData) []CollectibleDetails {
res := make([]CollectibleDetails, 0, len(data))
for _, c := range data {
res = append(res, fullCollectibleDataToDetails(c))
}
return res
}

View File

@ -223,6 +223,11 @@ func (s *Service) SetCollectibleMetadataProvider(provider thirdparty.Collectible
s.collectiblesManager.SetMetadataProvider(provider) s.collectiblesManager.SetMetadataProvider(provider)
} }
// Set external Collectibles community info provider
func (s *Service) SetCollectibleCommunityInfoProvider(provider thirdparty.CollectibleCommunityInfoProvider) {
s.collectiblesManager.SetCommunityInfoProvider(provider)
}
// Stop reactor and close db. // Stop reactor and close db.
func (s *Service) Stop() error { func (s *Service) Stop() error {
log.Info("wallet will be stopped") log.Info("wallet will be stopped")

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/protocol/communities/token"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
w_common "github.com/status-im/status-go/services/wallet/common" w_common "github.com/status-im/status-go/services/wallet/common"
) )
@ -76,12 +77,13 @@ type CollectionTrait struct {
// Collection info // Collection info
type CollectionData struct { type CollectionData struct {
ID ContractID `json:"id"` ID ContractID `json:"id"`
Provider string `json:"provider"` CommunityID string `json:"community_id"`
Name string `json:"name"` Provider string `json:"provider"`
Slug string `json:"slug"` Name string `json:"name"`
ImageURL string `json:"image_url"` Slug string `json:"slug"`
Traits map[string]CollectionTrait `json:"traits"` ImageURL string `json:"image_url"`
Traits map[string]CollectionTrait `json:"traits"`
} }
type CollectibleTrait struct { type CollectibleTrait struct {
@ -94,6 +96,7 @@ type CollectibleTrait struct {
// Collectible info // Collectible info
type CollectibleData struct { type CollectibleData struct {
ID CollectibleUniqueID `json:"id"` ID CollectibleUniqueID `json:"id"`
CommunityID string `json:"community_id"`
Provider string `json:"provider"` Provider string `json:"provider"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
@ -141,11 +144,28 @@ func (c *FullCollectibleDataContainer) ToOwnershipContainer() CollectibleOwnersh
} }
} }
// Community-related info. Present only for collectibles minted in a community.
// This info is directly fetched every time upon request since a change in community
// settings could affect it.
type CollectiblesCommunityInfo struct {
CommunityID string `json:"community_id"`
CommunityName string `json:"community_name"`
CommunityColor string `json:"community_color"`
CommunityImage string `json:"community_image"`
PrivilegesLevel token.PrivilegesLevel `json:"privileges_level"`
}
type CollectibleMetadataProvider interface { type CollectibleMetadataProvider interface {
CanProvideCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (bool, error) CanProvideCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (bool, error)
FetchCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (*FullCollectibleData, error) FetchCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (*FullCollectibleData, error)
} }
type CollectibleCommunityInfoProvider interface {
FetchCollectibleCommunityInfo(communityID string, id CollectibleUniqueID) (*CollectiblesCommunityInfo, error)
FetchCollectibleCommunityTraits(communityID string, id CollectibleUniqueID) ([]CollectibleTrait, error)
}
type TokenBalance struct { type TokenBalance struct {
TokenID *bigint.BigInt `json:"tokenId"` TokenID *bigint.BigInt `json:"tokenId"`
Balance *bigint.BigInt `json:"balance"` Balance *bigint.BigInt `json:"balance"`

View File

@ -5,6 +5,7 @@
// 1692701339_add_scope_to_pending.up.sql (576B) // 1692701339_add_scope_to_pending.up.sql (576B)
// 1694540071_add_collectibles_ownership_update_timestamp.up.sql (349B) // 1694540071_add_collectibles_ownership_update_timestamp.up.sql (349B)
// 1694692748_add_raw_balance_to_token_balances.up.sql (165B) // 1694692748_add_raw_balance_to_token_balances.up.sql (165B)
// 1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql (275B)
// doc.go (74B) // doc.go (74B)
package migrations package migrations
@ -15,7 +16,6 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -25,7 +25,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) { func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data)) gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil { if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err) return nil, fmt.Errorf("read %q: %w", name, err)
} }
var buf bytes.Buffer var buf bytes.Buffer
@ -33,7 +33,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close() clErr := gz.Close()
if err != nil { if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err) return nil, fmt.Errorf("read %q: %w", name, err)
} }
if clErr != nil { if clErr != nil {
return nil, err return nil, err
@ -89,7 +89,7 @@ func _1691753758_initialUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1691753758_initial.up.sql", size: 5738, mode: os.FileMode(0664), modTime: time.Unix(1694793410, 0)} info := bindataFileInfo{name: "1691753758_initial.up.sql", size: 5738, mode: os.FileMode(0644), modTime: time.Unix(1695161107, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6b, 0x25, 0x31, 0xc8, 0x27, 0x3, 0x6b, 0x9f, 0x15, 0x42, 0x2f, 0x85, 0xfb, 0xe3, 0x6, 0xea, 0xf7, 0x97, 0x12, 0x56, 0x3c, 0x9a, 0x5b, 0x1a, 0xca, 0xb1, 0x23, 0xfa, 0xcd, 0x57, 0x25, 0x5c}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6b, 0x25, 0x31, 0xc8, 0x27, 0x3, 0x6b, 0x9f, 0x15, 0x42, 0x2f, 0x85, 0xfb, 0xe3, 0x6, 0xea, 0xf7, 0x97, 0x12, 0x56, 0x3c, 0x9a, 0x5b, 0x1a, 0xca, 0xb1, 0x23, 0xfa, 0xcd, 0x57, 0x25, 0x5c}}
return a, nil return a, nil
} }
@ -109,7 +109,7 @@ func _1692701329_add_collectibles_and_collections_data_cacheUpSql() (*asset, err
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1692701329_add_collectibles_and_collections_data_cache.up.sql", size: 1808, mode: os.FileMode(0664), modTime: time.Unix(1694793410, 0)} info := bindataFileInfo{name: "1692701329_add_collectibles_and_collections_data_cache.up.sql", size: 1808, mode: os.FileMode(0644), modTime: time.Unix(1695161107, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0x51, 0xf4, 0x2b, 0x92, 0xde, 0x59, 0x65, 0xd8, 0x9b, 0x57, 0xe0, 0xfd, 0x7b, 0x12, 0xb, 0x29, 0x6e, 0x9d, 0xb5, 0x90, 0xe, 0xfa, 0x12, 0x97, 0xd, 0x61, 0x60, 0x7f, 0x32, 0x1d, 0xc3}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0x51, 0xf4, 0x2b, 0x92, 0xde, 0x59, 0x65, 0xd8, 0x9b, 0x57, 0xe0, 0xfd, 0x7b, 0x12, 0xb, 0x29, 0x6e, 0x9d, 0xb5, 0x90, 0xe, 0xfa, 0x12, 0x97, 0xd, 0x61, 0x60, 0x7f, 0x32, 0x1d, 0xc3}}
return a, nil return a, nil
} }
@ -129,7 +129,7 @@ func _1692701339_add_scope_to_pendingUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1692701339_add_scope_to_pending.up.sql", size: 576, mode: os.FileMode(0664), modTime: time.Unix(1694793410, 0)} info := bindataFileInfo{name: "1692701339_add_scope_to_pending.up.sql", size: 576, mode: os.FileMode(0644), modTime: time.Unix(1695161107, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x36, 0x8a, 0x5e, 0xe2, 0x63, 0x15, 0x37, 0xba, 0x55, 0x18, 0xf3, 0xcc, 0xe0, 0x5, 0x84, 0xe1, 0x5b, 0xe8, 0x1, 0x32, 0x6b, 0x9f, 0x7d, 0x9f, 0xd9, 0x23, 0x6c, 0xa9, 0xb5, 0xdc, 0xf4, 0x93}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x36, 0x8a, 0x5e, 0xe2, 0x63, 0x15, 0x37, 0xba, 0x55, 0x18, 0xf3, 0xcc, 0xe0, 0x5, 0x84, 0xe1, 0x5b, 0xe8, 0x1, 0x32, 0x6b, 0x9f, 0x7d, 0x9f, 0xd9, 0x23, 0x6c, 0xa9, 0xb5, 0xdc, 0xf4, 0x93}}
return a, nil return a, nil
} }
@ -149,7 +149,7 @@ func _1694540071_add_collectibles_ownership_update_timestampUpSql() (*asset, err
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1694540071_add_collectibles_ownership_update_timestamp.up.sql", size: 349, mode: os.FileMode(0664), modTime: time.Unix(1695198641, 0)} info := bindataFileInfo{name: "1694540071_add_collectibles_ownership_update_timestamp.up.sql", size: 349, mode: os.FileMode(0644), modTime: time.Unix(1695161107, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7f, 0x45, 0xc7, 0xce, 0x79, 0x63, 0xbc, 0x6f, 0x83, 0x5f, 0xe2, 0x3, 0x56, 0xcc, 0x5, 0x2f, 0x85, 0xda, 0x7e, 0xea, 0xf5, 0xd2, 0xac, 0x19, 0xd4, 0xd8, 0x5e, 0xdd, 0xed, 0xe2, 0xa9, 0x97}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7f, 0x45, 0xc7, 0xce, 0x79, 0x63, 0xbc, 0x6f, 0x83, 0x5f, 0xe2, 0x3, 0x56, 0xcc, 0x5, 0x2f, 0x85, 0xda, 0x7e, 0xea, 0xf5, 0xd2, 0xac, 0x19, 0xd4, 0xd8, 0x5e, 0xdd, 0xed, 0xe2, 0xa9, 0x97}}
return a, nil return a, nil
} }
@ -169,11 +169,31 @@ func _1694692748_add_raw_balance_to_token_balancesUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1694692748_add_raw_balance_to_token_balances.up.sql", size: 165, mode: os.FileMode(0664), modTime: time.Unix(1695203924, 0)} info := bindataFileInfo{name: "1694692748_add_raw_balance_to_token_balances.up.sql", size: 165, mode: os.FileMode(0644), modTime: time.Unix(1695211597, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd4, 0xe0, 0x5b, 0x42, 0xf0, 0x96, 0xa5, 0xf5, 0xed, 0xc0, 0x97, 0x88, 0xb0, 0x6d, 0xfe, 0x7d, 0x97, 0x2e, 0x17, 0xd2, 0x16, 0xbc, 0x2a, 0xf2, 0xcc, 0x67, 0x9e, 0xc5, 0x47, 0xf6, 0x69, 0x1}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd4, 0xe0, 0x5b, 0x42, 0xf0, 0x96, 0xa5, 0xf5, 0xed, 0xc0, 0x97, 0x88, 0xb0, 0x6d, 0xfe, 0x7d, 0x97, 0x2e, 0x17, 0xd2, 0x16, 0xbc, 0x2a, 0xf2, 0xcc, 0x67, 0x9e, 0xc5, 0x47, 0xf6, 0x69, 0x1}}
return a, nil return a, nil
} }
var __1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xce\xcf\xc9\x49\x4d\x2e\xc9\x4c\xca\x49\x8d\x4f\x49\x2c\x49\x8c\x4f\x4e\x4c\xce\x48\x55\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x48\xce\xcf\xcd\x2d\xcd\xcb\x2c\xa9\x8c\xcf\x4c\x51\x08\x71\x8d\x08\x51\xf0\xf3\x0f\x51\xf0\x0b\xf5\xf1\x51\x70\x71\x75\x73\x0c\xf5\x09\x51\x50\x52\xb2\xe6\x0a\x0d\x70\x71\x0c\xc1\x69\x5e\xb0\x6b\x08\xaa\x41\xb6\x60\x4d\x5c\xd8\x9c\x92\x9f\x47\x55\x97\xa0\x1a\x87\xc3\x21\x80\x00\x00\x00\xff\xff\x2e\x30\x6f\xa7\x13\x01\x00\x00")
func _1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSqlBytes() ([]byte, error) {
return bindataRead(
__1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql,
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql",
)
}
func _1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql() (*asset, error) {
bytes, err := _1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql", size: 275, mode: os.FileMode(0644), modTime: time.Unix(1695211597, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xfa, 0x2, 0xa, 0x7f, 0x4b, 0xd1, 0x3, 0xd0, 0x3, 0x29, 0x84, 0x31, 0xed, 0x49, 0x4f, 0xb1, 0x2d, 0xd7, 0x80, 0x41, 0x5b, 0xfa, 0x6, 0xae, 0xb4, 0xf6, 0x6b, 0x49, 0xee, 0x57, 0x33, 0x76}}
return a, nil
}
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00") var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00")
func docGoBytes() ([]byte, error) { func docGoBytes() ([]byte, error) {
@ -189,7 +209,7 @@ func docGo() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0664), modTime: time.Unix(1694793410, 0)} info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1695161107, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xde, 0x7c, 0x28, 0xcd, 0x47, 0xf2, 0xfa, 0x7c, 0x51, 0x2d, 0xd8, 0x38, 0xb, 0xb0, 0x34, 0x9d, 0x4c, 0x62, 0xa, 0x9e, 0x28, 0xc3, 0x31, 0x23, 0xd9, 0xbb, 0x89, 0x9f, 0xa0, 0x89, 0x1f, 0xe8}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xde, 0x7c, 0x28, 0xcd, 0x47, 0xf2, 0xfa, 0x7c, 0x51, 0x2d, 0xd8, 0x38, 0xb, 0xb0, 0x34, 0x9d, 0x4c, 0x62, 0xa, 0x9e, 0x28, 0xc3, 0x31, 0x23, 0xd9, 0xbb, 0x89, 0x9f, 0xa0, 0x89, 0x1f, 0xe8}}
return a, nil return a, nil
} }
@ -285,28 +305,29 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name. // _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){ var _bindata = map[string]func() (*asset, error){
"1691753758_initial.up.sql": _1691753758_initialUpSql, "1691753758_initial.up.sql": _1691753758_initialUpSql,
"1692701329_add_collectibles_and_collections_data_cache.up.sql": _1692701329_add_collectibles_and_collections_data_cacheUpSql,
"1692701329_add_collectibles_and_collections_data_cache.up.sql": _1692701329_add_collectibles_and_collections_data_cacheUpSql, "1692701339_add_scope_to_pending.up.sql": _1692701339_add_scope_to_pendingUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": _1694540071_add_collectibles_ownership_update_timestampUpSql,
"1692701339_add_scope_to_pending.up.sql": _1692701339_add_scope_to_pendingUpSql, "1694692748_add_raw_balance_to_token_balances.up.sql": _1694692748_add_raw_balance_to_token_balancesUpSql,
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": _1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": _1694540071_add_collectibles_ownership_update_timestampUpSql,
"1694692748_add_raw_balance_to_token_balances.up.sql": _1694692748_add_raw_balance_to_token_balancesUpSql,
"doc.go": docGo, "doc.go": docGo,
} }
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain // AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata. // directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the // For example if you run go-bindata on data/... and data contains the
// following hierarchy: // following hierarchy:
// data/ //
// foo.txt // data/
// img/ // foo.txt
// a.png // img/
// b.png // a.png
// b.png
//
// then AssetDir("data") would return []string{"foo.txt", "img"}, // then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"}, // AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and // AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
@ -339,12 +360,13 @@ type bintree struct {
} }
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"1691753758_initial.up.sql": &bintree{_1691753758_initialUpSql, map[string]*bintree{}}, "1691753758_initial.up.sql": {_1691753758_initialUpSql, map[string]*bintree{}},
"1692701329_add_collectibles_and_collections_data_cache.up.sql": &bintree{_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}}, "1692701329_add_collectibles_and_collections_data_cache.up.sql": {_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1692701339_add_scope_to_pending.up.sql": &bintree{_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}}, "1692701339_add_scope_to_pending.up.sql": {_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}},
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": &bintree{_1694540071_add_collectibles_ownership_update_timestampUpSql, map[string]*bintree{}}, "1694540071_add_collectibles_ownership_update_timestamp.up.sql": {_1694540071_add_collectibles_ownership_update_timestampUpSql, map[string]*bintree{}},
"1694692748_add_raw_balance_to_token_balances.up.sql": &bintree{_1694692748_add_raw_balance_to_token_balancesUpSql, map[string]*bintree{}}, "1694692748_add_raw_balance_to_token_balances.up.sql": {_1694692748_add_raw_balance_to_token_balancesUpSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}}, "1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": {_1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"doc.go": {docGo, map[string]*bintree{}},
}} }}
// RestoreAsset restores an asset under the given directory. // RestoreAsset restores an asset under the given directory.
@ -361,7 +383,7 @@ func RestoreAsset(dir, name string) error {
if err != nil { if err != nil {
return err return err
} }
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) err = os.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,5 @@
ALTER TABLE collectible_data_cache ADD COLUMN community_id TEXT NOT NULL DEFAULT "";
UPDATE collectible_data_cache SET community_id = "";
ALTER TABLE collection_data_cache ADD COLUMN community_id TEXT NOT NULL DEFAULT "";
UPDATE collection_data_cache SET community_id = "";