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  }