github.com/status-im/status-go@v1.1.0/services/wallet/thirdparty/alchemy/types.go (about)

     1  package alchemy
     2  
     3  import (
     4  	"encoding/json"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/ethereum/go-ethereum/common"
     9  
    10  	"github.com/status-im/status-go/services/wallet/bigint"
    11  	walletCommon "github.com/status-im/status-go/services/wallet/common"
    12  	"github.com/status-im/status-go/services/wallet/thirdparty"
    13  
    14  	"golang.org/x/text/cases"
    15  	"golang.org/x/text/language"
    16  )
    17  
    18  const AlchemyID = "alchemy"
    19  
    20  type TokenBalance struct {
    21  	TokenID *bigint.BigInt `json:"tokenId"`
    22  	Balance *bigint.BigInt `json:"balance"`
    23  }
    24  
    25  type CollectibleOwner struct {
    26  	OwnerAddress  common.Address `json:"ownerAddress"`
    27  	TokenBalances []TokenBalance `json:"tokenBalances"`
    28  }
    29  
    30  type CollectibleContractOwnership struct {
    31  	Owners  []CollectibleOwner `json:"owners"`
    32  	PageKey string             `json:"pageKey"`
    33  }
    34  
    35  func alchemyCollectibleOwnersToCommon(alchemyOwners []CollectibleOwner) []thirdparty.CollectibleOwner {
    36  	owners := make([]thirdparty.CollectibleOwner, 0, len(alchemyOwners))
    37  	for _, alchemyOwner := range alchemyOwners {
    38  		balances := make([]thirdparty.TokenBalance, 0, len(alchemyOwner.TokenBalances))
    39  
    40  		for _, alchemyBalance := range alchemyOwner.TokenBalances {
    41  			balances = append(balances, thirdparty.TokenBalance{
    42  				TokenID: &bigint.BigInt{Int: alchemyBalance.TokenID.Int},
    43  				Balance: alchemyBalance.Balance,
    44  			})
    45  		}
    46  		owner := thirdparty.CollectibleOwner{
    47  			OwnerAddress:  alchemyOwner.OwnerAddress,
    48  			TokenBalances: balances,
    49  		}
    50  
    51  		owners = append(owners, owner)
    52  	}
    53  	return owners
    54  }
    55  
    56  type AttributeValue string
    57  
    58  func (st *AttributeValue) UnmarshalJSON(b []byte) error {
    59  	var item interface{}
    60  	if err := json.Unmarshal(b, &item); err != nil {
    61  		return err
    62  	}
    63  
    64  	switch v := item.(type) {
    65  	case float64:
    66  		*st = AttributeValue(strconv.FormatFloat(v, 'f', 2, 64))
    67  	case int:
    68  		*st = AttributeValue(strconv.Itoa(v))
    69  	case string:
    70  		*st = AttributeValue(v)
    71  	}
    72  	return nil
    73  }
    74  
    75  type Attribute struct {
    76  	TraitType string         `json:"trait_type"`
    77  	Value     AttributeValue `json:"value"`
    78  }
    79  
    80  type RawMetadata struct {
    81  	Attributes []Attribute `json:"attributes"`
    82  }
    83  
    84  type RawFull struct {
    85  	RawMetadata RawMetadata `json:"metadata"`
    86  }
    87  
    88  type Raw struct {
    89  	RawMetadata interface{} `json:"metadata"`
    90  }
    91  
    92  func (r *Raw) UnmarshalJSON(b []byte) error {
    93  	raw := RawFull{
    94  		RawMetadata{
    95  			Attributes: make([]Attribute, 0),
    96  		},
    97  	}
    98  
    99  	// Field structure is not known in advance
   100  	_ = json.Unmarshal(b, &raw)
   101  
   102  	r.RawMetadata = raw.RawMetadata
   103  	return nil
   104  }
   105  
   106  type OpenSeaMetadata struct {
   107  	ImageURL        string `json:"imageUrl"`
   108  	TwitterUsername string `json:"twitterUsername"`
   109  	ExternalURL     string `json:"externalUrl"`
   110  }
   111  
   112  type Contract struct {
   113  	Address         common.Address  `json:"address"`
   114  	Name            string          `json:"name"`
   115  	Symbol          string          `json:"symbol"`
   116  	TokenType       string          `json:"tokenType"`
   117  	OpenSeaMetadata OpenSeaMetadata `json:"openseaMetadata"`
   118  }
   119  
   120  type ContractList struct {
   121  	Contracts []Contract `json:"contracts"`
   122  }
   123  
   124  type Image struct {
   125  	ImageURL             string `json:"pngUrl"`
   126  	CachedAnimationURL   string `json:"cachedUrl"`
   127  	OriginalAnimationURL string `json:"originalUrl"`
   128  }
   129  
   130  type Asset struct {
   131  	Contract    Contract       `json:"contract"`
   132  	TokenID     *bigint.BigInt `json:"tokenId"`
   133  	Name        string         `json:"name"`
   134  	Description string         `json:"description"`
   135  	Image       Image          `json:"image"`
   136  	Raw         Raw            `json:"raw"`
   137  	TokenURI    string         `json:"tokenUri"`
   138  	Balance     *bigint.BigInt `json:"balance,omitempty"`
   139  }
   140  
   141  type OwnedNFTList struct {
   142  	OwnedNFTs  []Asset        `json:"ownedNfts"`
   143  	TotalCount *bigint.BigInt `json:"totalCount"`
   144  	PageKey    string         `json:"pageKey"`
   145  }
   146  
   147  type NFTList struct {
   148  	NFTs []Asset `json:"nfts"`
   149  }
   150  
   151  type BatchContractAddresses struct {
   152  	Addresses []common.Address `json:"contractAddresses"`
   153  }
   154  
   155  type BatchTokenIDs struct {
   156  	IDs []TokenID `json:"tokens"`
   157  }
   158  
   159  type TokenID struct {
   160  	ContractAddress common.Address `json:"contractAddress"`
   161  	TokenID         *bigint.BigInt `json:"tokenId"`
   162  }
   163  
   164  func alchemyToCollectibleTraits(attributes []Attribute) []thirdparty.CollectibleTrait {
   165  	ret := make([]thirdparty.CollectibleTrait, 0, len(attributes))
   166  	caser := cases.Title(language.Und, cases.NoLower)
   167  	for _, orig := range attributes {
   168  		dest := thirdparty.CollectibleTrait{
   169  			TraitType: strings.Replace(orig.TraitType, "_", " ", 1),
   170  			Value:     caser.String(string(orig.Value)),
   171  		}
   172  
   173  		ret = append(ret, dest)
   174  	}
   175  	return ret
   176  }
   177  
   178  func alchemyToContractType(tokenType string) walletCommon.ContractType {
   179  	switch tokenType {
   180  	case "ERC721":
   181  		return walletCommon.ContractTypeERC721
   182  	case "ERC1155":
   183  		return walletCommon.ContractTypeERC1155
   184  	default:
   185  		return walletCommon.ContractTypeUnknown
   186  	}
   187  }
   188  
   189  func (c *Contract) toCollectionSocials() *thirdparty.CollectionSocials {
   190  	return &thirdparty.CollectionSocials{
   191  		Website:       c.OpenSeaMetadata.ExternalURL,
   192  		TwitterHandle: c.OpenSeaMetadata.TwitterUsername,
   193  		Provider:      AlchemyID,
   194  	}
   195  }
   196  
   197  func (c *Contract) toCollectionData(id thirdparty.ContractID) thirdparty.CollectionData {
   198  	ret := thirdparty.CollectionData{
   199  		ID:           id,
   200  		ContractType: alchemyToContractType(c.TokenType),
   201  		Provider:     AlchemyID,
   202  		Name:         c.Name,
   203  		ImageURL:     c.OpenSeaMetadata.ImageURL,
   204  		Traits:       make(map[string]thirdparty.CollectionTrait, 0),
   205  		Socials:      c.toCollectionSocials(),
   206  	}
   207  	return ret
   208  }
   209  
   210  func (c *Asset) toCollectiblesData(id thirdparty.CollectibleUniqueID) thirdparty.CollectibleData {
   211  	rawMetadata := c.Raw.RawMetadata.(RawMetadata)
   212  
   213  	return thirdparty.CollectibleData{
   214  		ID:           id,
   215  		ContractType: alchemyToContractType(c.Contract.TokenType),
   216  		Provider:     AlchemyID,
   217  		Name:         c.Name,
   218  		Description:  c.Description,
   219  		ImageURL:     c.Image.ImageURL,
   220  		AnimationURL: c.Image.CachedAnimationURL,
   221  		Traits:       alchemyToCollectibleTraits(rawMetadata.Attributes),
   222  		TokenURI:     c.TokenURI,
   223  	}
   224  }
   225  
   226  func (c *Asset) toCommon(id thirdparty.CollectibleUniqueID) thirdparty.FullCollectibleData {
   227  	contractData := c.Contract.toCollectionData(id.ContractID)
   228  	return thirdparty.FullCollectibleData{
   229  		CollectibleData: c.toCollectiblesData(id),
   230  		CollectionData:  &contractData,
   231  		AccountBalance:  c.Balance,
   232  	}
   233  }
   234  
   235  func alchemyToCollectiblesData(chainID walletCommon.ChainID, l []Asset) []thirdparty.FullCollectibleData {
   236  	ret := make([]thirdparty.FullCollectibleData, 0, len(l))
   237  	for _, asset := range l {
   238  		id := thirdparty.CollectibleUniqueID{
   239  			ContractID: thirdparty.ContractID{
   240  				ChainID: chainID,
   241  				Address: asset.Contract.Address,
   242  			},
   243  			TokenID: asset.TokenID,
   244  		}
   245  		item := asset.toCommon(id)
   246  		ret = append(ret, item)
   247  	}
   248  	return ret
   249  }
   250  
   251  func alchemyToCollectionsData(chainID walletCommon.ChainID, l []Contract) []thirdparty.CollectionData {
   252  	ret := make([]thirdparty.CollectionData, 0, len(l))
   253  	for _, contract := range l {
   254  		id := thirdparty.ContractID{
   255  			ChainID: chainID,
   256  			Address: contract.Address,
   257  		}
   258  		item := contract.toCollectionData(id)
   259  		ret = append(ret, item)
   260  	}
   261  	return ret
   262  }