github.com/status-im/status-go@v1.1.0/services/wallet/thirdparty/collectible_types.go (about) 1 package thirdparty 2 3 import ( 4 "context" 5 "database/sql" 6 "errors" 7 "fmt" 8 "math/big" 9 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/status-im/status-go/protocol/communities/token" 12 "github.com/status-im/status-go/services/wallet/bigint" 13 w_common "github.com/status-im/status-go/services/wallet/common" 14 ) 15 16 var ( 17 ErrChainIDNotSupported = errors.New("chainID not supported") 18 ErrEndpointNotSupported = errors.New("endpoint not supported") 19 ) 20 21 const FetchNoLimit = 0 22 const FetchFromStartCursor = "" 23 const FetchFromAnyProvider = "" 24 25 type CollectibleProvider interface { 26 ID() string 27 IsChainSupported(chainID w_common.ChainID) bool 28 IsConnected() bool 29 } 30 31 type ContractID struct { 32 ChainID w_common.ChainID `json:"chainID"` 33 Address common.Address `json:"address"` 34 } 35 36 func (k *ContractID) HashKey() string { 37 return fmt.Sprintf("%d+%s", k.ChainID, k.Address.String()) 38 } 39 40 type CollectibleUniqueID struct { 41 ContractID ContractID `json:"contractID"` 42 TokenID *bigint.BigInt `json:"tokenID"` 43 } 44 45 func (k *CollectibleUniqueID) HashKey() string { 46 return fmt.Sprintf("%s+%s", k.ContractID.HashKey(), k.TokenID.String()) 47 } 48 49 func (k *CollectibleUniqueID) Same(other *CollectibleUniqueID) bool { 50 return k.ContractID.ChainID == other.ContractID.ChainID && k.ContractID.Address == other.ContractID.Address && k.TokenID.Cmp(other.TokenID.Int) == 0 51 } 52 53 func RowsToCollectibles(rows *sql.Rows) ([]CollectibleUniqueID, error) { 54 var ids []CollectibleUniqueID 55 for rows.Next() { 56 id := CollectibleUniqueID{ 57 TokenID: &bigint.BigInt{Int: big.NewInt(0)}, 58 } 59 err := rows.Scan( 60 &id.ContractID.ChainID, 61 &id.ContractID.Address, 62 (*bigint.SQLBigIntBytes)(id.TokenID.Int), 63 ) 64 if err != nil { 65 return nil, err 66 } 67 68 ids = append(ids, id) 69 } 70 71 return ids, nil 72 } 73 74 func GroupCollectibleUIDsByChainID(uids []CollectibleUniqueID) map[w_common.ChainID][]CollectibleUniqueID { 75 ret := make(map[w_common.ChainID][]CollectibleUniqueID) 76 77 for _, uid := range uids { 78 if _, ok := ret[uid.ContractID.ChainID]; !ok { 79 ret[uid.ContractID.ChainID] = make([]CollectibleUniqueID, 0, len(uids)) 80 } 81 ret[uid.ContractID.ChainID] = append(ret[uid.ContractID.ChainID], uid) 82 } 83 84 return ret 85 } 86 87 func GroupContractIDsByChainID(ids []ContractID) map[w_common.ChainID][]ContractID { 88 ret := make(map[w_common.ChainID][]ContractID) 89 90 for _, id := range ids { 91 if _, ok := ret[id.ChainID]; !ok { 92 ret[id.ChainID] = make([]ContractID, 0, len(ids)) 93 } 94 ret[id.ChainID] = append(ret[id.ChainID], id) 95 } 96 97 return ret 98 } 99 100 func GroupCollectiblesByChainID(collectibles []*FullCollectibleData) map[w_common.ChainID][]*FullCollectibleData { 101 ret := make(map[w_common.ChainID][]*FullCollectibleData) 102 103 for i, collectible := range collectibles { 104 chainID := collectible.CollectibleData.ID.ContractID.ChainID 105 if _, ok := ret[chainID]; !ok { 106 ret[chainID] = make([]*FullCollectibleData, 0, len(collectibles)) 107 } 108 ret[chainID] = append(ret[chainID], collectibles[i]) 109 } 110 111 return ret 112 } 113 114 func GroupCollectiblesByContractAddress(collectibles []*FullCollectibleData) map[common.Address][]*FullCollectibleData { 115 ret := make(map[common.Address][]*FullCollectibleData) 116 117 for i, collectible := range collectibles { 118 contractAddress := collectible.CollectibleData.ID.ContractID.Address 119 if _, ok := ret[contractAddress]; !ok { 120 ret[contractAddress] = make([]*FullCollectibleData, 0, len(collectibles)) 121 } 122 ret[contractAddress] = append(ret[contractAddress], collectibles[i]) 123 } 124 125 return ret 126 } 127 128 func GroupCollectiblesByChainIDAndContractAddress(collectibles []*FullCollectibleData) map[w_common.ChainID]map[common.Address][]*FullCollectibleData { 129 ret := make(map[w_common.ChainID]map[common.Address][]*FullCollectibleData) 130 131 collectiblesByChainID := GroupCollectiblesByChainID(collectibles) 132 for chainID, chainCollectibles := range collectiblesByChainID { 133 ret[chainID] = GroupCollectiblesByContractAddress(chainCollectibles) 134 } 135 136 return ret 137 } 138 139 type CollectionTrait struct { 140 Min float64 `json:"min"` 141 Max float64 `json:"max"` 142 } 143 144 // Collection info 145 type CollectionData struct { 146 ID ContractID `json:"id"` 147 ContractType w_common.ContractType `json:"contract_type"` 148 CommunityID string `json:"community_id"` 149 Provider string `json:"provider"` 150 Name string `json:"name"` 151 Slug string `json:"slug"` 152 ImageURL string `json:"image_url"` 153 ImagePayload []byte 154 Traits map[string]CollectionTrait `json:"traits"` 155 Socials *CollectionSocials `json:"socials"` 156 } 157 158 type CollectionSocials struct { 159 Website string `json:"website"` 160 TwitterHandle string `json:"twitter_handle"` 161 Provider string `json:"provider"` 162 } 163 164 type CollectibleTrait struct { 165 TraitType string `json:"trait_type"` 166 Value string `json:"value"` 167 DisplayType string `json:"display_type"` 168 MaxValue string `json:"max_value"` 169 } 170 171 // Collectible info 172 type CollectibleData struct { 173 ID CollectibleUniqueID `json:"id"` 174 ContractType w_common.ContractType `json:"contract_type"` 175 CommunityID string `json:"community_id"` 176 Provider string `json:"provider"` 177 Name string `json:"name"` 178 Description string `json:"description"` 179 Permalink string `json:"permalink"` 180 ImageURL string `json:"image_url"` 181 ImagePayload []byte 182 AnimationURL string `json:"animation_url"` 183 AnimationMediaType string `json:"animation_media_type"` 184 Traits []CollectibleTrait `json:"traits"` 185 BackgroundColor string `json:"background_color"` 186 TokenURI string `json:"token_uri"` 187 IsFirst bool `json:"is_first"` 188 Soulbound bool `json:"soulbound"` 189 } 190 191 // Community-related collectible info. Present only for collectibles minted in a community. 192 type CollectibleCommunityInfo struct { 193 PrivilegesLevel token.PrivilegesLevel `json:"privileges_level"` 194 } 195 196 // Combined Collection+Collectible info returned by the CollectibleProvider 197 // Some providers may not return the CollectionData in the same API call, so it's optional 198 type FullCollectibleData struct { 199 CollectibleData CollectibleData 200 CollectionData *CollectionData 201 CommunityInfo *CommunityInfo 202 CollectibleCommunityInfo *CollectibleCommunityInfo 203 Ownership []AccountBalance // This is a list of all the owners of the collectible 204 AccountBalance *bigint.BigInt // This is the balance of the collectible for the requested account 205 } 206 207 type CollectiblesContainer[T any] struct { 208 Items []T 209 NextCursor string 210 PreviousCursor string 211 Provider string 212 } 213 214 type CollectibleOwnershipContainer CollectiblesContainer[CollectibleIDBalance] 215 type CollectionDataContainer CollectiblesContainer[CollectionData] 216 type CollectibleDataContainer CollectiblesContainer[CollectibleData] 217 type FullCollectibleDataContainer CollectiblesContainer[FullCollectibleData] 218 219 // Tried to find a way to make this generic, but couldn't, so the code below is duplicated somewhere else 220 func collectibleItemsToBalances(items []FullCollectibleData) []CollectibleIDBalance { 221 ret := make([]CollectibleIDBalance, 0, len(items)) 222 for _, item := range items { 223 balance := CollectibleIDBalance{ 224 ID: item.CollectibleData.ID, 225 Balance: item.AccountBalance, 226 } 227 ret = append(ret, balance) 228 } 229 return ret 230 } 231 232 func (c *FullCollectibleDataContainer) ToOwnershipContainer() CollectibleOwnershipContainer { 233 return CollectibleOwnershipContainer{ 234 Items: collectibleItemsToBalances(c.Items), 235 NextCursor: c.NextCursor, 236 PreviousCursor: c.PreviousCursor, 237 Provider: c.Provider, 238 } 239 } 240 241 type CollectibleIDBalance struct { 242 ID CollectibleUniqueID `json:"id"` 243 Balance *bigint.BigInt `json:"balance"` 244 } 245 246 type TokenBalance struct { 247 TokenID *bigint.BigInt `json:"tokenId"` 248 Balance *bigint.BigInt `json:"balance"` 249 } 250 251 type TokenBalancesPerContractAddress = map[common.Address][]TokenBalance 252 253 type CollectibleOwner struct { 254 OwnerAddress common.Address `json:"ownerAddress"` 255 TokenBalances []TokenBalance `json:"tokenBalances"` 256 } 257 258 type CollectibleContractOwnership struct { 259 ContractAddress common.Address `json:"contractAddress"` 260 Owners []CollectibleOwner `json:"owners"` 261 } 262 263 type AccountBalance struct { 264 Address common.Address `json:"address"` 265 Balance *bigint.BigInt `json:"balance"` 266 TxTimestamp int64 `json:"txTimestamp"` 267 } 268 269 type CollectibleContractOwnershipProvider interface { 270 CollectibleProvider 271 FetchCollectibleOwnersByContractAddress(ctx context.Context, chainID w_common.ChainID, contractAddress common.Address) (*CollectibleContractOwnership, error) 272 } 273 274 type CollectibleAccountOwnershipProvider interface { 275 CollectibleProvider 276 FetchAllAssetsByOwner(ctx context.Context, chainID w_common.ChainID, owner common.Address, cursor string, limit int) (*FullCollectibleDataContainer, error) 277 FetchAllAssetsByOwnerAndContractAddress(ctx context.Context, chainID w_common.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int) (*FullCollectibleDataContainer, error) 278 } 279 280 type CollectibleDataProvider interface { 281 CollectibleProvider 282 FetchAssetsByCollectibleUniqueID(ctx context.Context, uniqueIDs []CollectibleUniqueID) ([]FullCollectibleData, error) 283 FetchCollectionSocials(ctx context.Context, contractID ContractID) (*CollectionSocials, error) 284 } 285 286 type CollectionDataProvider interface { 287 CollectibleProvider 288 FetchCollectionsDataByContractID(ctx context.Context, ids []ContractID) ([]CollectionData, error) 289 } 290 291 type CollectibleSearchProvider interface { 292 CollectibleProvider 293 SearchCollections(ctx context.Context, chainID w_common.ChainID, text string, cursor string, limit int) (*CollectionDataContainer, error) 294 SearchCollectibles(ctx context.Context, chainID w_common.ChainID, collections []common.Address, text string, cursor string, limit int) (*FullCollectibleDataContainer, error) 295 } 296 297 type CollectibleProviders struct { 298 ContractOwnershipProviders []CollectibleContractOwnershipProvider 299 AccountOwnershipProviders []CollectibleAccountOwnershipProvider 300 CollectibleDataProviders []CollectibleDataProvider 301 CollectionDataProviders []CollectionDataProvider 302 SearchProviders []CollectibleSearchProvider 303 } 304 305 func (p *CollectibleProviders) GetProviderList() []CollectibleProvider { 306 ret := make([]CollectibleProvider, 0) 307 308 uniqueProviders := make(map[string]CollectibleProvider) 309 for _, provider := range p.ContractOwnershipProviders { 310 uniqueProviders[provider.ID()] = provider 311 } 312 for _, provider := range p.AccountOwnershipProviders { 313 uniqueProviders[provider.ID()] = provider 314 } 315 for _, provider := range p.CollectibleDataProviders { 316 uniqueProviders[provider.ID()] = provider 317 } 318 for _, provider := range p.CollectionDataProviders { 319 uniqueProviders[provider.ID()] = provider 320 } 321 for _, provider := range p.SearchProviders { 322 uniqueProviders[provider.ID()] = provider 323 } 324 325 for _, provider := range uniqueProviders { 326 ret = append(ret, provider) 327 } 328 329 return ret 330 }