├── .gitignore ├── LICENSE ├── README.md ├── requirements.txt ├── setup.py └── solapi ├── __init__.py ├── magic_eden ├── __init__.py ├── official_api │ ├── __init__.py │ ├── base.py │ ├── collections.py │ ├── launchpad.py │ ├── tokens.py │ ├── utils │ │ ├── __init__.py │ │ ├── consts.py │ │ ├── data.py │ │ └── types.py │ └── wallets.py └── site_api │ ├── __init__.py │ ├── collection.py │ └── utils │ ├── __init__.py │ ├── consts.py │ ├── data.py │ └── types.py ├── solanart ├── __init__.py ├── api │ ├── __init__.py │ └── collection.py └── utils │ ├── __init__.py │ ├── consts.py │ ├── data.py │ └── types.py └── utils ├── __init__.py ├── api.py ├── converters.py ├── data.py └── datetime.py /.gitignore: -------------------------------------------------------------------------------- 1 | .pyc 2 | __pycache__ 3 | venv 4 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ivannnnnnnnnn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python wrap for solana NFT marketplaces api such as MagicEden and Solanart 2 | 3 | ```pip install git+https://github.com/ivannnnnnnnnn/solapi.git``` 4 | 5 | ## How to use? 6 | 7 | 1. Import api class 8 | 2. Create class instance 9 | 3. Use methods of class 10 | 11 | ### Important 12 | 13 | ##### Method ended with ```_dirty``` its clear answer from marketplace 14 | 15 | ##### Other methods, contain processed data and correspond to type annotations 16 | 17 | 18 | ## Example 19 | 20 | ``` 21 | from solapi.magic_eden.site_api.collection import MagicEdenCollectionApi 22 | 23 | magic_eden_api = MagicEdenCollectionApi() 24 | 25 | collections = magic_eden_api.get_collection_list() 26 | 27 | ``` 28 | 29 | ### You can also import types for annotation 30 | 31 | ``` 32 | from solapi.magic_eden.site_api.utils.types import MECollectionInfo 33 | from typings import List 34 | 35 | collections: List[MECollectionInfo] = [] 36 | ``` 37 | 38 | ### Available api classes and methods for use site api 39 | ``` 40 | 41 | MagicEdeneCollectionApi: solapi.magic_eden.site_api.collection.MagicEdeneCollectionApi 42 | TypeAnnotations: solapi.magic_eden.site_api.utils.types 43 | 44 | MagicEdenCollectionApi: 45 | get_collection_list_dirty(): List[Dict] 46 | get_collection_list(): List[MECollectionInfo] 47 | get_collection_list_stats_dirty(): List[Dict] 48 | get_collection_list_stats(): List[MECollectionMetrics] 49 | get_collection_info_dirty(symbol:str): Dict 50 | get_collection_info(symbol:str): MECollectionInfo 51 | get_collection_stats_dirty(symbol:str): Dict 52 | get_collection_stats(symbol:str): MECollectionStats 53 | 54 | 55 | 56 | SolanartCollectionApi: solapi.solanart.api.collection.SolanartCollectionApi 57 | TypeAnnotations: solapi.solanart.utils.types 58 | 59 | SolanartCollectionApi: 60 | get_collection_list_dirty(): List[Dict] 61 | get_collection_list(): List[SACollectionInfo] 62 | get_collection_list_stats_dirty(): List[Dict] 63 | get_collection_list_stats(): List[SACollectionStats] 64 | 65 | ``` 66 | 67 | ### Official MagicEden api python wrapper 68 | 69 | ``` 70 | from solapi.magic_eden.official_api import ( 71 | MagicEdenTokensApi, 72 | MagicEdenWalletsApi, 73 | MagicEdenCollectionsApi, 74 | MagicEdenLaunchpadApi 75 | ) 76 | ``` 77 | 78 | #### Api classes have methods corresponded to endpoints from official [docs](https://api.magiceden.dev/) 79 | 80 | #### Type annotations: 81 | ``` 82 | from solapi.magic_eden.official_api.utils.types import * 83 | ``` 84 | 85 | #### Constructor of official api classes take one parameter (environment,) 86 | ``` 87 | wallet_api = MagicEdenWalletsApi(environment = 'DEVNENT') # by default value = 'MAINNET' 88 | ``` 89 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.26.0 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name='solapi', 5 | packages=find_packages(), 6 | version='0.0.5', 7 | description='Python wrap for solana NFT marketplaces such as MagicEden and Solanart', 8 | author='ivan.srshtn.crypto@gmail.com', 9 | license='MIT', 10 | install_requires=[ 11 | 'requests==2.26.0' 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /solapi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/__init__.py -------------------------------------------------------------------------------- /solapi/magic_eden/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/magic_eden/__init__.py -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .tokens import MagicEdenTokensApi 2 | from .wallets import MagicEdenWalletsApi 3 | from .collections import MagicEdenCollectionsApi 4 | from .launchpad import MagicEdenLaunchpadApi 5 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/base.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from solapi.magic_eden.official_api.utils.consts import MEUrlBuilder 4 | from solapi.magic_eden.official_api.utils.types import MagicEdenAPIEnvironment 5 | from solapi.utils.api import BaseApi 6 | 7 | 8 | class MagicEdenOfficialApi(BaseApi): 9 | url_builder_class: Type[MEUrlBuilder] 10 | url_builder: MEUrlBuilder 11 | 12 | def __init__(self, environment: MagicEdenAPIEnvironment = 'MAINNET'): 13 | self.url_builder = self.url_builder_class(environment) 14 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/collections.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict 2 | 3 | from solapi.magic_eden.official_api.base import MagicEdenOfficialApi 4 | from solapi.magic_eden.official_api.utils.consts import MEAPICollectionsUrlsBuilder 5 | from solapi.magic_eden.official_api.utils.data import listing_response_mapper, activity_response_mapper, \ 6 | collection_stats_mapper 7 | from solapi.magic_eden.official_api.utils.types import MECollection, MEActivity, MECollectionStats 8 | from solapi.utils.data import list_map 9 | 10 | 11 | class MEListingItem(object): 12 | pass 13 | 14 | 15 | class MagicEdenCollectionsApi(MagicEdenOfficialApi): 16 | url_builder_class = MEAPICollectionsUrlsBuilder 17 | url_builder: MEAPICollectionsUrlsBuilder 18 | 19 | def get_collections_dirty(self, offset: int = 0, limit: int = 100) -> List[Dict]: 20 | url = self.url_builder.collections(offset, limit) 21 | return self._get_request(url) 22 | 23 | def get_collections(self, offset: int = 0, limit: int = 100) -> List[MECollection]: 24 | return self.get_collections_dirty(offset, limit) 25 | 26 | def listings_dirty(self, symbol: str, offset: int = 0, limit: int = 100) -> List[Dict]: 27 | url = self.url_builder.listings(symbol, offset, limit) 28 | return self._get_request(url) 29 | 30 | def listings(self, symbol: str, offset: int = 0, limit: int = 100) -> List[MEListingItem]: 31 | dirty = self.listings_dirty(symbol, offset, limit) 32 | return list_map(listing_response_mapper, dirty) 33 | 34 | def activities_dirty(self, symbol: str, offset: int = 0, limit: int = 100) -> List[Dict]: 35 | url = self.url_builder.activities(symbol, offset, limit) 36 | return self._get_request(url) 37 | 38 | def activities(self, symbol: str, offset: int = 0, limit: int = 100) -> List[MEActivity]: 39 | dirty = self.activities_dirty(symbol, offset, limit) 40 | return list_map(activity_response_mapper, dirty) 41 | 42 | def stats_dirty(self, symbol: str) -> Dict: 43 | url = self.url_builder.stats(symbol) 44 | return self._get_request(url) 45 | 46 | def stats(self, symbol) -> MECollectionStats: 47 | dirty = self.stats_dirty(symbol) 48 | return None if dirty is None else collection_stats_mapper(dirty) 49 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/launchpad.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict 2 | 3 | from solapi.magic_eden.official_api.base import MagicEdenOfficialApi 4 | from solapi.magic_eden.official_api.utils.consts import MEAPILaunchpadUrlsBuilder 5 | from solapi.magic_eden.official_api.utils.data import launchpad_collection_mapper 6 | from solapi.magic_eden.official_api.utils.types import MELaunchpadCollection 7 | from solapi.utils.data import list_map 8 | 9 | 10 | class MagicEdenLaunchpadApi(MagicEdenOfficialApi): 11 | url_builder_class = MEAPILaunchpadUrlsBuilder 12 | url_builder: MEAPILaunchpadUrlsBuilder 13 | 14 | def collections_dirty(self, offset: int = 0, limit: int = 100) -> List[Dict]: 15 | url = self.url_builder.collections(offset, limit) 16 | return self._get_request(url) 17 | 18 | def collections(self, offset: int = 0, limit: int = 100) -> List[MELaunchpadCollection]: 19 | dirty = self.collections_dirty(offset, limit) 20 | return list_map(launchpad_collection_mapper, dirty) 21 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/tokens.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | from solapi.magic_eden.official_api.base import MagicEdenOfficialApi 4 | from solapi.magic_eden.official_api.utils.consts import MEAPITokensUrlBuilder 5 | from solapi.magic_eden.official_api.utils.data import token_response_mapper, listing_response_mapper, \ 6 | offer_response_mapper, activity_response_mapper 7 | from solapi.magic_eden.official_api.utils.types import METoken, MEListingItem, MEOffer, MEActivity 8 | from solapi.utils.data import list_map 9 | 10 | 11 | class MagicEdenTokensApi(MagicEdenOfficialApi): 12 | url_builder_class = MEAPITokensUrlBuilder 13 | url_builder: MEAPITokensUrlBuilder 14 | 15 | def get_token_dirty(self, token: str) -> Dict: 16 | url = self.url_builder.get_token(token) 17 | return self._get_request(url) 18 | 19 | def get_token(self, token: str) -> METoken: 20 | dirty = self.get_token_dirty(token) 21 | if dirty: 22 | return token_response_mapper(dirty) 23 | 24 | def listings_dirty(self, token: str) -> List[Dict]: 25 | url = self.url_builder.listings(token) 26 | return self._get_request(url) 27 | 28 | def listings(self, token: str) -> List[MEListingItem]: 29 | dirty = self.listings_dirty(token) 30 | return list_map(listing_response_mapper, dirty) 31 | 32 | def offer_received_dirty(self, token: str, offset: int = 0, limit: int = 100) -> List[Dict]: 33 | url = self.url_builder.offer_received(token, offset, limit) 34 | return self._get_request(url) 35 | 36 | def offer_received(self, token: str, offset: int = 0, limit: int = 100) -> List[MEOffer]: 37 | dirty = self.offer_received_dirty(token, offset, limit) 38 | return list_map(offer_response_mapper, dirty) 39 | 40 | def activities_dirty(self, token: str, offset: int = 0, limit: int = 100) -> List[Dict]: 41 | url = self.url_builder.activities(token, offset, limit) 42 | return self._get_request(url) 43 | 44 | def activities(self, token: str, offset: int = 0, limit: int = 100) -> List[MEActivity]: 45 | dirty = self.activities_dirty(token, offset, limit) 46 | return list_map(activity_response_mapper, dirty) 47 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/magic_eden/official_api/utils/__init__.py -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/utils/consts.py: -------------------------------------------------------------------------------- 1 | from solapi.magic_eden.official_api.utils.types import MagicEdenAPIEnvironment 2 | 3 | 4 | class MEUrlBuilder: 5 | _endpoint: str 6 | 7 | def __init__(self, environment: MagicEdenAPIEnvironment = 'MAINNET'): 8 | if environment == 'DEVNET': 9 | self._endpoint = 'https://api-devnet.magiceden.dev/v2' 10 | elif environment == 'MAINNET': 11 | self._endpoint = 'https://api-mainnet.magiceden.dev/v2' 12 | else: 13 | raise ValueError(f'Invalid environment expected {MagicEdenAPIEnvironment} but get {environment}') 14 | 15 | 16 | class MEAPITokensUrlBuilder(MEUrlBuilder): 17 | def get_token(self, token: str): 18 | return f'{self._endpoint}/tokens/{token}' 19 | 20 | def listings(self, token): 21 | return f'{self._endpoint}/tokens/{token}/listings' 22 | 23 | def offer_received(self, token, offset: int = 0, limit: int = 100): 24 | return f'{self._endpoint}/tokens/{token}/offer_received?offset={offset}&limit={limit}' 25 | 26 | def activities(self, token, offset: int = 0, limit: int = 100): 27 | return f'{self._endpoint}/tokens/{token}/activities?offset={offset}&limit={limit}' 28 | 29 | 30 | class MEAPIWalletUrlsBuilder(MEUrlBuilder): 31 | def tokens(self, wallet_address: str, offset: int = 0, limit: int = 100, listed_only: bool = None): 32 | res = f'{self._endpoint}/wallets/{wallet_address}/tokens' \ 33 | f'?offset={offset}&limit={limit}' 34 | if listed_only is not None: 35 | res = f'{res}&listedOnly={str(listed_only).lower()}' 36 | return res 37 | 38 | def activities(self, wallet_address: str, offset: int = 0, limit: int = 100): 39 | return f'{self._endpoint}/wallets/{wallet_address}/activities?offset={offset}&limit={limit}' 40 | 41 | def offers_made(self, wallet_address: str, offset: int = 0, limit: int = 100): 42 | return f'{self._endpoint}/wallets/{wallet_address}/activities?offset={offset}&limit={limit}' 43 | 44 | def offers_received(self, wallet_address: str, offset: int = 0, limit: int = 100): 45 | return f'{self._endpoint}/wallets/{wallet_address}/offers_received?offset={offset}&limit={limit}' 46 | 47 | def escrow_balance(self, wallet_address: str): 48 | return f'{self._endpoint}/wallets/{wallet_address}/escrow_balance' 49 | 50 | 51 | class MEAPICollectionsUrlsBuilder(MEUrlBuilder): 52 | def collections(self, offset: int = 0, limit: int = 100): 53 | return f'{self._endpoint}/collections?offset={offset}&limit={limit}' 54 | 55 | def listings(self, symbol: str, offset: int = 0, limit: int = 100): 56 | return f'{self._endpoint}/collections/{symbol}/listings?offset={offset}&limit={limit}' 57 | 58 | def activities(self, symbol: str, offset: int = 0, limit: int = 100): 59 | return f'{self._endpoint}/collections/{symbol}/activities?offset={offset}&limit={limit}' 60 | 61 | def stats(self, symbol: str, offset: int = 0, limit: int = 100): 62 | return f'{self._endpoint}/collections/{symbol}/stats?offset={offset}&limit={limit}' 63 | 64 | 65 | class MEAPILaunchpadUrlsBuilder(MEUrlBuilder): 66 | def collections(self, offset: int = 0, limit: int = 100): 67 | return f'{self._endpoint}/launchpad/collections?offset={offset}&limit={limit}' 68 | 69 | 70 | class METokenResponse: 71 | mint_address = 'mintAddress' 72 | owner = 'owner' 73 | supply = 'supply' 74 | delegate = 'delegate' 75 | symbol = 'collection' 76 | name = 'name' 77 | update_authority = 'updateAuthority' 78 | primary_sale_happened = 'primarySaleHappened' 79 | seller_fee_bases_point = 'sellerFeeBasesPoint' 80 | image = 'image' 81 | animation_url = 'animationUrl' 82 | external_url = 'externalUrl' 83 | attributes = 'attributes' 84 | properties = 'properties' 85 | 86 | 87 | class MEActivityResponse: 88 | signature = 'signature' 89 | type = 'type' 90 | source = 'source' 91 | token_mint = 'tokenMint' 92 | symbol = 'collection' 93 | slot = 'slot' 94 | block_time = 'blockTime' 95 | buyer = 'buyer' 96 | buyer_referral = 'buyerReferral' 97 | seller = 'seller' 98 | seller_referral = 'sellerReferral' 99 | price = 'price' 100 | 101 | 102 | class MEOfferResponse: 103 | pda_address = 'pdaAddress' 104 | token_mint = 'tokenMint' 105 | auction_house = 'auctionHouse' 106 | buyer = 'buyer' 107 | price = 'price' 108 | token_size = 'tokenSize' 109 | expiry = 'expiry' 110 | 111 | 112 | class MEEscrowBalanceResponse: 113 | buyer_escrow = 'buyerEscrow' 114 | balance = 'balance' 115 | 116 | 117 | class MEListingResponse: 118 | pda_address = 'pdaAddress' 119 | auction_house = 'auctionHouse' 120 | token_address = 'tokenAddress' 121 | token_mint = 'tokenMint' 122 | seller = 'seller' 123 | token_size = 'tokenSize' 124 | price = 'price' 125 | 126 | 127 | class MECollectionStatsResponse: 128 | symbol = 'symbol' 129 | floor_price = 'floorPrice' 130 | listed_count = 'listedCount' 131 | volume_all = 'volumeAll' 132 | 133 | 134 | class MELaunchpadCollectionResponse: 135 | symbol = 'symbol' 136 | name = 'name' 137 | description = 'description' 138 | featured = 'featured' 139 | edition = 'edition' 140 | image = 'image' 141 | price = 'price' 142 | supply = 'size' 143 | launch_datetime = 'launchDatetime' 144 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/utils/data.py: -------------------------------------------------------------------------------- 1 | from solapi.magic_eden.official_api.utils.consts import METokenResponse, MEActivityResponse, MEOfferResponse, \ 2 | MEEscrowBalanceResponse, MEListingResponse, MECollectionStatsResponse, MELaunchpadCollectionResponse 3 | from solapi.magic_eden.official_api.utils.types import METoken, MEActivity, MEOffer, MEEscrowBalance, MEListingItem, \ 4 | MECollectionStats 5 | from solapi.utils.converters import convert_sol_absolute 6 | from solapi.utils.datetime import parse_timestamp, parse_date 7 | 8 | 9 | def token_response_mapper(data: dict) -> METoken: 10 | return { 11 | 'mint_address': data.get(METokenResponse.mint_address, ''), 12 | 'owner': data.get(METokenResponse.owner, ''), 13 | 'supply': data.get(METokenResponse.supply, 0), 14 | 'delegate': data.get(METokenResponse.delegate, ''), 15 | 'symbol': data.get(METokenResponse.symbol, ''), 16 | 'name': data.get(METokenResponse.name, ''), 17 | 'update_authority': data.get(METokenResponse.update_authority, ''), 18 | 'primary_sale_happened': data.get(METokenResponse.primary_sale_happened, 0), 19 | 'seller_fee_basis_points': data.get(METokenResponse.seller_fee_bases_point, 0), 20 | 'image': data.get(METokenResponse.image, ''), 21 | 'animation_url': data.get(METokenResponse.animation_url, None), 22 | 'external_url': data.get(METokenResponse.external_url, ''), 23 | 'attributes': data.get(METokenResponse.attributes, []), 24 | 'properties': data.get(METokenResponse.properties, {}) 25 | } 26 | 27 | 28 | _ACTIVITY_TYPE_MAPPING = { 29 | 'bid': 'BID', 30 | 'list': 'LIST', 31 | 'buyNow': 'BUY_NOW', 32 | 'cancelBid': 'CANCEL_BID', 33 | 'delist': 'DELIST', 34 | } 35 | 36 | 37 | def activity_response_mapper(data: dict) -> MEActivity: 38 | _type = data.get(MEActivityResponse.type, None) 39 | if _type: 40 | _type = _ACTIVITY_TYPE_MAPPING.get(_type, None) 41 | return { 42 | 'signature': data.get(MEActivityResponse.signature, ''), 43 | 'type': _type, 44 | 'source': data.get(MEActivityResponse.source, ''), 45 | 'token_mint': data.get(MEActivityResponse.token_mint, ''), 46 | 'symbol': data.get(MEActivityResponse.symbol, ''), 47 | 'slot': data.get(MEActivityResponse.slot, ''), 48 | 'block_time': parse_timestamp(data.get(MEActivityResponse.block_time, 0)), 49 | 'buyer': data.get(MEActivityResponse.buyer, ''), 50 | 'buyer_referral': data.get(MEActivityResponse.buyer_referral, ''), 51 | 'seller': data.get(MEActivityResponse.seller, ''), 52 | 'seller_referal': data.get(MEActivityResponse.seller_referral, ''), 53 | 'price': data.get(MEActivityResponse.price, 0), 54 | } 55 | 56 | 57 | def offer_response_mapper(data: dict) -> MEOffer: 58 | return { 59 | 'pda_address': data.get(MEOfferResponse.pda_address, ''), 60 | 'token_mint': data.get(MEOfferResponse.token_mint, ''), 61 | 'auction_house': data.get(MEOfferResponse.auction_house, ''), 62 | 'buyer': data.get(MEOfferResponse.buyer, ''), 63 | 'price': data.get(MEOfferResponse.price, 0), 64 | 'token_size': data.get(MEOfferResponse.token_size, 0), 65 | 'expiry': data.get(MEOfferResponse.expiry, 0) 66 | } 67 | 68 | 69 | def escrow_balance_response_mapper(data: dict) -> MEEscrowBalance: 70 | return { 71 | 'buyer_escrow': data.get(MEEscrowBalanceResponse.buyer_escrow, ''), 72 | 'balance': data.get(MEEscrowBalanceResponse.balance, 0) 73 | } 74 | 75 | 76 | def listing_response_mapper(data: dict) -> MEListingItem: 77 | return { 78 | 'pda_address': data.get(MEListingResponse.pda_address, ''), 79 | 'auction_house': data.get(MEListingResponse.auction_house, ''), 80 | 'token_address': data.get(MEListingResponse.token_address, ''), 81 | 'token_mint': data.get(MEListingResponse.token_mint, ''), 82 | 'seller': data.get(MEListingResponse.seller, ''), 83 | 'token_size': data.get(MEListingResponse.token_size, 0), 84 | 'price': data.get(MEListingResponse.price, 0) 85 | } 86 | 87 | 88 | def collection_stats_mapper(data: dict) -> MECollectionStats: 89 | return { 90 | 'symbol': data.get(MECollectionStatsResponse.symbol, ''), 91 | 'floor_price': convert_sol_absolute(data.get(MECollectionStatsResponse.floor_price, 0)), 92 | 'listed_count': data.get(MECollectionStatsResponse.listed_count, 0), 93 | 'volume_all': convert_sol_absolute(data.get(MECollectionStatsResponse.volume_all, 0)) 94 | } 95 | 96 | 97 | def launchpad_collection_mapper(data: dict): 98 | return { 99 | 'symbol': data.get(MELaunchpadCollectionResponse.symbol, ''), 100 | 'name': data.get(MELaunchpadCollectionResponse.name, ''), 101 | 'description': data.get(MELaunchpadCollectionResponse.description, ''), 102 | 'featured': data.get(MELaunchpadCollectionResponse.featured, None), 103 | 'edition': data.get(MELaunchpadCollectionResponse.edition, ''), 104 | 'image': data.get(MELaunchpadCollectionResponse.image, ''), 105 | 'price': data.get(MELaunchpadCollectionResponse.price, 0), 106 | 'supply': data.get(MELaunchpadCollectionResponse.supply, 0), 107 | 'launch_datetime': parse_date(data.get(MELaunchpadCollectionResponse.launch_datetime)) 108 | } 109 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/utils/types.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Literal, TypedDict, List, Optional 3 | 4 | MagicEdenAPIEnvironment = Literal['DEVNET ', 'MAINNET'] 5 | 6 | 7 | class METokenAttribute(TypedDict): 8 | trait_type: str 9 | value: str 10 | 11 | 12 | class METokenFileProperty(TypedDict): 13 | uri: str 14 | type: str 15 | 16 | 17 | class METokenCreatorProperty(TypedDict): 18 | address: str 19 | share: int 20 | 21 | 22 | class METokenProperties(TypedDict): 23 | files: List[METokenFileProperty] 24 | category: str 25 | creators: List[METokenCreatorProperty] 26 | 27 | 28 | class METoken(TypedDict): 29 | mint_address: str 30 | owner: str 31 | supply: int 32 | delegate: str 33 | symbol: str 34 | name: str 35 | update_authority: str 36 | primary_sale_happened: int 37 | seller_fee_basis_points: int 38 | image: str 39 | animation_url: Optional[str] 40 | external_url: str 41 | attributes: List[METokenAttribute] 42 | properties: METokenProperties 43 | 44 | 45 | MEActivityType = Literal['BID', 'LIST', 'DELIST', 'BUY_NOW', 'CANCEL_BID'] 46 | MEActivitySource = Literal['magiceden', 'magiceden_v2'] 47 | 48 | 49 | class MEActivity(TypedDict): 50 | signature: str 51 | type: MEActivityType 52 | source: MEActivitySource 53 | token_mint: str 54 | symbol: str 55 | slot: int 56 | block_time: datetime 57 | buyer: str 58 | buyer_referral: str 59 | seller: str 60 | seller_referral: str 61 | price: float 62 | 63 | 64 | class MEOffer(TypedDict): 65 | pda_address: str 66 | token_mint: str 67 | auction_house: str 68 | buyer: str 69 | price: float 70 | token_size: int 71 | expiry: int 72 | 73 | 74 | class MEEscrowBalance(TypedDict): 75 | buyer_escrow: str 76 | balance: float 77 | 78 | 79 | class MEListingItem(TypedDict): 80 | pda_address: str 81 | auction_house: str 82 | token_address: str 83 | token_mint: str 84 | seller: str 85 | token_size: int 86 | price: float 87 | 88 | 89 | class MECollection(TypedDict): 90 | symbol: str 91 | name: str 92 | description: str 93 | image: str 94 | twitter: Optional[str] 95 | discord: Optional[str] 96 | website: Optional[str] 97 | categories: List[str] 98 | 99 | 100 | class MECollectionStats(TypedDict): 101 | symbol: str 102 | floor_price: float 103 | listed_count: int 104 | volume_all: float 105 | 106 | 107 | class MELaunchpadCollection(TypedDict): 108 | symbol: str 109 | name: str 110 | description: str 111 | featured: Optional[bool] 112 | edition: str 113 | image: str 114 | price: float 115 | supply: int 116 | launch_datetime: datetime 117 | -------------------------------------------------------------------------------- /solapi/magic_eden/official_api/wallets.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict 2 | 3 | from solapi.magic_eden.official_api.base import MagicEdenOfficialApi 4 | from solapi.magic_eden.official_api.utils.consts import MEAPIWalletUrlsBuilder 5 | from solapi.magic_eden.official_api.utils.data import token_response_mapper, activity_response_mapper, \ 6 | offer_response_mapper, \ 7 | escrow_balance_response_mapper 8 | from solapi.magic_eden.official_api.utils.types import MEActivity, METoken, MEOffer, MEEscrowBalance 9 | from solapi.utils.data import list_map 10 | 11 | 12 | class MagicEdenWalletsApi(MagicEdenOfficialApi): 13 | url_builder_class = MEAPIWalletUrlsBuilder 14 | url_builder: MEAPIWalletUrlsBuilder 15 | 16 | def tokens_dirty(self, wallet_address: str, offset: int = 0, limit: int = 100, 17 | listed_only: bool = False) -> List[Dict]: 18 | url = self.url_builder.tokens(wallet_address, offset, limit, listed_only) 19 | return self._get_request(url) 20 | 21 | def tokens(self, wallet_address: str, offset: int = 0, limit: int = 100, 22 | listed_only: bool = False) -> List[METoken]: 23 | dirty = self.tokens_dirty(wallet_address, offset, limit, listed_only) 24 | return list_map(token_response_mapper, dirty) 25 | 26 | def activities_dirty(self, wallet_address: str, offset: int = 0, limit: int = 100) -> List[Dict]: 27 | url = self.url_builder.activities(wallet_address, offset, limit) 28 | return self._get_request(url) 29 | 30 | def activities(self, wallet_address: str, offset: int = 0, limit: int = 100) -> List[MEActivity]: 31 | dirty = self.activities_dirty(wallet_address, offset, limit) 32 | return list_map(activity_response_mapper, dirty) 33 | 34 | def offers_made_dirty(self, wallet_address: str, offset: int = 0, limit: int = 100) -> List[Dict]: 35 | url = self.url_builder.offers_made(wallet_address, offset, limit) 36 | return self._get_request(url) 37 | 38 | def offers_made(self, wallet_address: str, offset: int = 0, limit: int = 100) -> List[MEOffer]: 39 | dirty = self.offers_made_dirty(wallet_address, offset, limit) 40 | return list_map(offer_response_mapper, dirty) 41 | 42 | def offers_received_dirty(self, wallet_address: str, offset: int = 0, limit: int = 100) -> List[Dict]: 43 | url = self.url_builder.offers_received(wallet_address, offset, limit) 44 | return self._get_request(url) 45 | 46 | def offers_received(self, wallet_address: str, offset: int = 0, limit: int = 100) -> List[MEOffer]: 47 | dirty = self.offers_received_dirty(wallet_address, offset, limit) 48 | return list_map(offer_response_mapper, dirty) 49 | 50 | def escrow_balance_dirty(self, wallet_address: str) -> Dict: 51 | url = self.url_builder.escrow_balance(wallet_address) 52 | return self._get_request(url) 53 | 54 | def escrow_balance(self, wallet_address: str) -> MEEscrowBalance: 55 | dirty = self.escrow_balance_dirty(wallet_address) 56 | if dirty: 57 | return escrow_balance_response_mapper(dirty) 58 | -------------------------------------------------------------------------------- /solapi/magic_eden/site_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/magic_eden/site_api/__init__.py -------------------------------------------------------------------------------- /solapi/magic_eden/site_api/collection.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, List 2 | 3 | from solapi.magic_eden.site_api.utils.consts import MEAPIUrls 4 | from solapi.magic_eden.site_api.utils.data import collection_stats_cleaner, collection_info_cleaner, \ 5 | collection_list_stats_cleaner 6 | from solapi.magic_eden.site_api.utils.types import MECollectionStats, MECollectionInfo, MECollectionMetrics 7 | from solapi.utils.api import BaseApi 8 | 9 | 10 | class MagicEdenCollectionApi(BaseApi): 11 | 12 | def get_collection_stats_dirty(self, symbol: str) -> Optional[Dict]: 13 | url = f'{MEAPIUrls.COLLECTION_STATS}{symbol}' 14 | res = self._get_request(url) 15 | return res.get('results') if isinstance(res, dict) else None 16 | 17 | def get_collection_info_dirty(self, symbol: str) -> Optional[Dict]: 18 | url = f'{MEAPIUrls.COLLECTION_INFO}{symbol}' 19 | res = self._get_request(url) 20 | return res if bool(res) else None 21 | 22 | def get_collection_stats(self, symbol: str) -> Optional[MECollectionStats]: 23 | data = self.get_collection_stats_dirty(symbol) 24 | if data: 25 | return collection_stats_cleaner(data) 26 | 27 | def get_collection_info(self, symbol: str) -> Optional[MECollectionInfo]: 28 | data = self.get_collection_info_dirty(symbol) 29 | if data: 30 | return collection_info_cleaner(data) 31 | 32 | def get_collection_list_stats_dirty(self): 33 | url = MEAPIUrls.COLLECTION_LIST_STATS 34 | res = self._get_request(url) 35 | return res.get('results') if isinstance(res, dict) else None 36 | 37 | def get_collection_list_stats(self) -> Optional[List[MECollectionMetrics]]: 38 | data = self.get_collection_list_stats_dirty() 39 | if data: 40 | return list(map(lambda x: collection_list_stats_cleaner(x), data)) 41 | 42 | def get_collection_list_dirty(self): 43 | url = MEAPIUrls.COLLECTION_LIST 44 | res = self._get_request(url) 45 | return res.get('collections') if isinstance(res, dict) else None 46 | 47 | def get_collection_list(self) -> Optional[List[MECollectionInfo]]: 48 | data = self.get_collection_list_dirty() 49 | if data: 50 | return list(map(lambda x: collection_info_cleaner(x), data)) 51 | -------------------------------------------------------------------------------- /solapi/magic_eden/site_api/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/magic_eden/site_api/utils/__init__.py -------------------------------------------------------------------------------- /solapi/magic_eden/site_api/utils/consts.py: -------------------------------------------------------------------------------- 1 | class MEAPIUrls: 2 | COLLECTION_STATS = 'https://api-mainnet.magiceden.io/rpc/getCollectionEscrowStats/' 3 | COLLECTION_INFO = 'https://api-mainnet.magiceden.io/collections/' 4 | COLLECTION_LIST = 'https://api-mainnet.magiceden.io/all_collections' 5 | COLLECTION_LIST_STATS = 'https://api-mainnet.magiceden.io/rpc/getAggregatedCollectionMetrics' 6 | 7 | 8 | class MEResponseConstsCollectionStats: 9 | # STATS 10 | LISTED_COUNT = 'listedCount' 11 | FLOOR_PRICE = 'floorPrice' 12 | LISTED_TOTAL_VALUE = 'listedTotalValue' 13 | DAY_AVG_PRICE = 'avgPrice24hr' 14 | DAY_VOLUME = 'volume24hr' 15 | TOTAL_VOLUME = 'volumeAll' 16 | 17 | 18 | class MEResponseConstsCollectionInfo: 19 | # INFO 20 | SYMBOL = 'symbol' 21 | NAME = 'name' 22 | IMAGE = 'image' 23 | DESCRIPTION = 'description' 24 | CREATED = 'createdAt' 25 | WEBSITE = 'website' 26 | TWITTER = 'twitter' 27 | DISCORD = 'discord' 28 | HAS_ALL_ITEMS = 'hasAllItems' 29 | SUPPLY = 'totalItems' 30 | 31 | 32 | class MeResponseConstsCollectionListStats: 33 | SYMBOL = 'symbol' 34 | TOTAL_VOLUME = 'txVolumeME.valueAT' 35 | DAILY_VOLUME = 'txVolumeME.value1d' 36 | WEEKLY_VOLUME = 'txVolumeME.value1d' 37 | MONTHLY_VOLUME = 'txVolumeME.value30d' 38 | PREV_DAILY_VOLUME = 'txVolumeME.prev1d' 39 | PREV_WEEKLY_VOLUME = 'txVolumeME.prev3d' 40 | PREV_MONTHLY_VOLUME = 'txVolumeME.prev30d' 41 | AVG_PRICE = 'avgPrice.valueAT' 42 | DAILY_AVG_PRICE = 'avgPrice.value1d' 43 | WEEKLY_AVG_PRICE = 'avgPrice.value7d' 44 | MONTHLY_AVG_PRICE = 'avgPrice.value30d' 45 | PREV_DAILY_AVG_PRICE = 'avgPrice.prev1d' 46 | PREV_WEEKLY_AVG_PRICE = 'avgPrice.prev7d' 47 | PREV_MONTHLY_AVG_PRICE = 'avgPrice.prev30d' 48 | FLOOR_PRICE = 'floorPrice.valueAT' 49 | PREV_DAILY_FLOOR_PRICE = 'floorPrice.prev1d' 50 | PREV_WEEKLY_FLOOR_PRICE = 'floorPrice.prev7d' 51 | PREV_MONTHLY_FLOOR_PRICE = 'floorPrice.prev30d' 52 | GLOBAL_VOLUME = 'txVolume.valueAT' 53 | GLOBAL_DAILY_VOLUME = 'txVolume.value1d' 54 | GLOBAL_WEEKLY_VOLUME = 'txVolume.value7d' 55 | GLOBAL_MONTHLY_VOLUME = 'txVolume.value30d' 56 | PREV_DAILY_GLOBAL_VOLUME = 'txVolume.prev1d' 57 | PREV_WEEKLY_GLOBAL_VOLUME = 'txVolume.prev7d' 58 | PREV_MONTHLY_GLOBAL_VOLUME = 'txVolume.prev30d' 59 | -------------------------------------------------------------------------------- /solapi/magic_eden/site_api/utils/data.py: -------------------------------------------------------------------------------- 1 | from solapi.magic_eden.site_api.utils.consts import ( 2 | MEResponseConstsCollectionStats, 3 | MEResponseConstsCollectionInfo, 4 | MeResponseConstsCollectionListStats as metrics 5 | ) 6 | from solapi.magic_eden.site_api.utils.types import MECollectionInfo, MECollectionMetrics, MECollectionStats 7 | from solapi.utils.converters import convert_sol_absolute 8 | from solapi.utils.data import dict_get_recursive_safe 9 | from solapi.utils.datetime import parse_date 10 | 11 | 12 | def collection_stats_cleaner(item: dict) -> MECollectionStats: 13 | return { 14 | 'listed_count': item.get(MEResponseConstsCollectionStats.LISTED_COUNT), 15 | 'floor_price': convert_sol_absolute(item.get(MEResponseConstsCollectionStats.FLOOR_PRICE)), 16 | 'listed_total_value': convert_sol_absolute(item.get(MEResponseConstsCollectionStats.LISTED_TOTAL_VALUE)), 17 | 'day_avg_price': convert_sol_absolute(item.get(MEResponseConstsCollectionStats.DAY_AVG_PRICE)), 18 | 'day_volume': convert_sol_absolute(item.get(MEResponseConstsCollectionStats.DAY_VOLUME)), 19 | 'total_volume': convert_sol_absolute(item.get(MEResponseConstsCollectionStats.TOTAL_VOLUME)), 20 | } 21 | 22 | 23 | def collection_info_cleaner(item: dict) -> MECollectionInfo: 24 | return { 25 | 'symbol': item.get(MEResponseConstsCollectionInfo.SYMBOL), 26 | 'name': item.get(MEResponseConstsCollectionInfo.NAME), 27 | 'img': item.get(MEResponseConstsCollectionInfo.IMAGE), 28 | 'description': item.get(MEResponseConstsCollectionInfo.DESCRIPTION), 29 | 'created': parse_date(item.get(MEResponseConstsCollectionInfo.CREATED)), 30 | 'website': item.get(MEResponseConstsCollectionInfo.WEBSITE), 31 | 'twitter': item.get(MEResponseConstsCollectionInfo.TWITTER), 32 | 'discord': item.get(MEResponseConstsCollectionInfo.DISCORD), 33 | 'has_all_items': item.get(MEResponseConstsCollectionInfo.HAS_ALL_ITEMS), 34 | 'supply': item.get(MEResponseConstsCollectionInfo.SUPPLY), 35 | } 36 | 37 | 38 | def collection_list_stats_cleaner(item: dict) -> MECollectionMetrics: 39 | return { 40 | 'symbol': dict_get_recursive_safe(item, metrics.SYMBOL), 41 | 'total_volume': dict_get_recursive_safe(item, metrics.TOTAL_VOLUME), 42 | 'daily_volume': dict_get_recursive_safe(item, metrics.DAILY_VOLUME), 43 | 'weekly_volume': dict_get_recursive_safe(item, metrics.WEEKLY_VOLUME), 44 | 'monthly_volume': dict_get_recursive_safe(item, metrics.MONTHLY_VOLUME), 45 | 'prev_daily_volume': dict_get_recursive_safe(item, metrics.PREV_DAILY_VOLUME), 46 | 'prev_weekly_volume': dict_get_recursive_safe(item, metrics.PREV_WEEKLY_VOLUME), 47 | 'prev_monthly_volume': dict_get_recursive_safe(item, metrics.PREV_MONTHLY_VOLUME), 48 | 'avg_price': dict_get_recursive_safe(item, metrics.AVG_PRICE), 49 | 'daily_avg_price': dict_get_recursive_safe(item, metrics.DAILY_AVG_PRICE), 50 | 'weekly_avg_price': dict_get_recursive_safe(item, metrics.WEEKLY_AVG_PRICE), 51 | 'monthly_avg_price': dict_get_recursive_safe(item, metrics.MONTHLY_AVG_PRICE), 52 | 'prev_daily_avg_price': dict_get_recursive_safe(item, metrics.PREV_DAILY_AVG_PRICE), 53 | 'prev_weekly_avg_price': dict_get_recursive_safe(item, metrics.PREV_WEEKLY_AVG_PRICE), 54 | 'prev_monthly_avg_price': dict_get_recursive_safe(item, metrics.PREV_MONTHLY_AVG_PRICE), 55 | 'floor_price': dict_get_recursive_safe(item, metrics.FLOOR_PRICE), 56 | 'prev_daily_floor_price': dict_get_recursive_safe(item, metrics.PREV_DAILY_FLOOR_PRICE), 57 | 'prev_weekly_floor_price': dict_get_recursive_safe(item, metrics.PREV_WEEKLY_FLOOR_PRICE), 58 | 'prev_monthly_floor_price': dict_get_recursive_safe(item, metrics.PREV_MONTHLY_FLOOR_PRICE), 59 | 'global_volume': dict_get_recursive_safe(item, metrics.GLOBAL_VOLUME), 60 | 'global_daily_volume': dict_get_recursive_safe(item, metrics.GLOBAL_DAILY_VOLUME), 61 | 'global_weekly_volume': dict_get_recursive_safe(item, metrics.GLOBAL_WEEKLY_VOLUME), 62 | 'global_monthly_volume': dict_get_recursive_safe(item, metrics.GLOBAL_MONTHLY_VOLUME), 63 | 'prev_daily_global_volume': dict_get_recursive_safe(item, metrics.PREV_DAILY_GLOBAL_VOLUME), 64 | 'prev_weekly_global_volume': dict_get_recursive_safe(item, metrics.PREV_WEEKLY_GLOBAL_VOLUME), 65 | 'prev_monthly_global_volume': dict_get_recursive_safe(item, metrics.PREV_MONTHLY_GLOBAL_VOLUME) 66 | } 67 | -------------------------------------------------------------------------------- /solapi/magic_eden/site_api/utils/types.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import TypedDict 3 | 4 | 5 | class MECollectionStats(TypedDict): 6 | listed_count: int 7 | floor_price: float 8 | listed_total_value: float 9 | day_avg_price: float 10 | day_volume: float 11 | total_volume: float 12 | 13 | 14 | class MECollectionInfo(TypedDict): 15 | symbol: str 16 | name: str 17 | img: str 18 | description: str 19 | created: datetime 20 | website: str 21 | twitter: str 22 | discord: str 23 | has_all_items: bool 24 | supply: int 25 | 26 | 27 | class MECollectionMetrics(TypedDict): 28 | symbol: str 29 | total_volume: float 30 | daily_volume: float 31 | weekly_volume: float 32 | monthly_volume: float 33 | prev_daily_volume: float 34 | prev_weekly_volume: float 35 | prev_monthly_volume: float 36 | avg_price: float 37 | daily_avg_price: float 38 | weekly_avg_price: float 39 | monthly_avg_price: float 40 | prev_daily_avg_price: float 41 | prev_weekly_avg_price: float 42 | prev_monthly_avg_price: float 43 | floor_price: float 44 | prev_daily_floor_price: float 45 | prev_weekly_floor_price: float 46 | prev_monthly_floor_price: float 47 | global_volume: float 48 | global_daily_volume: float 49 | global_weekly_volume: float 50 | global_monthly_volume: float 51 | prev_daily_global_volume: float 52 | prev_weekly_global_volume: float 53 | prev_monthly_global_volume: float 54 | -------------------------------------------------------------------------------- /solapi/solanart/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/solanart/__init__.py -------------------------------------------------------------------------------- /solapi/solanart/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/solanart/api/__init__.py -------------------------------------------------------------------------------- /solapi/solanart/api/collection.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List, Dict 2 | 3 | from solapi.solanart.utils.consts import SAAPIUrls 4 | from solapi.solanart.utils.data import collection_list_cleaner, collection_list_stats_cleaner 5 | from solapi.solanart.utils.types import SACollectionInfo, SACollectionStats 6 | from solapi.utils.api import BaseApi 7 | 8 | 9 | class SolanartCollectionApi(BaseApi): 10 | 11 | def get_collection_list_dirty(self) -> Optional[List[Dict]]: 12 | url = SAAPIUrls.COLLECTION_LIST 13 | return self._get_request(url) 14 | 15 | def get_collection_list(self) -> Optional[List[SACollectionInfo]]: 16 | data = self.get_collection_list_dirty() 17 | if data: 18 | return list(map(collection_list_cleaner, data)) 19 | 20 | def get_collection_list_stats_dirty(self) -> Optional[List[Dict]]: 21 | url = SAAPIUrls.COLLECTION_STATS_LIST 22 | return self._get_request(url) 23 | 24 | def get_collection_list_stats(self) -> Optional[List[SACollectionStats]]: 25 | data = self.get_collection_list_stats_dirty() 26 | if data: 27 | return list(map(collection_list_stats_cleaner, data)) 28 | -------------------------------------------------------------------------------- /solapi/solanart/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/solanart/utils/__init__.py -------------------------------------------------------------------------------- /solapi/solanart/utils/consts.py: -------------------------------------------------------------------------------- 1 | class SAAPIUrls: 2 | COLLECTION_LIST = 'https://qzlsklfacc.medianetwork.cloud/get_collections' 3 | COLLECTION_STATS_LIST = 'https://qzlsklfacc.medianetwork.cloud/query_volume_all' 4 | 5 | 6 | class SAResponseConstsCollectionInfo: 7 | # collection info 8 | ID = 'id' 9 | SYMBOL = 'url' 10 | NAME = 'name' 11 | DESCRIPTION = 'description' 12 | DISPLAY = 'display' 13 | NEW = 'new' 14 | SOON = 'soon' 15 | TRENDING = 'trending' 16 | CREATED = 'date' 17 | SUPPLY = 'supply' 18 | TWITTER = 'twitter' 19 | WEBSITE = 'website' 20 | DISCORD = 'discord' 21 | IMG = 'imgpreview' 22 | 23 | 24 | class SAResponseConstsCollectionStats: 25 | # collection stats 26 | TOTAL_VOLUME = 'totalVolume' 27 | DAILY_VOLUME = 'dailyVolume' 28 | WEEKLY_VOLUME = 'weeklyVolume' 29 | TOTAL_SALES = 'totalSales' 30 | DAILY_SALES = 'dailySales' 31 | WEEKLY_SALES = 'weeklySales' 32 | PREV_DAILY_SALES = 'prevDailySales' 33 | PREV_DAILY_VOLUME = 'prevDailyVolume' 34 | PREV_WEEKLY_SALES = 'prevWeeklySales' 35 | PREV_WEEKLY_VOLUME = 'prevWeeklyVolume' 36 | FLOOR_PRICE = 'floorPrice' 37 | OWNER_COUNT = 'ownerCount' 38 | SYMBOL = 'collection' 39 | LAST_UPDATE = 'lastUpdated' 40 | CATEGORY = 'category' 41 | -------------------------------------------------------------------------------- /solapi/solanart/utils/data.py: -------------------------------------------------------------------------------- 1 | from solapi.solanart.utils.consts import SAResponseConstsCollectionInfo, SAResponseConstsCollectionStats 2 | from solapi.solanart.utils.types import SACollectionInfo, SACollectionStats 3 | from solapi.utils.data import parse_str_status 4 | from solapi.utils.datetime import parse_timestamp 5 | 6 | 7 | def collection_list_cleaner(item: dict) -> SACollectionInfo: 8 | return { 9 | 'id': item.get(SAResponseConstsCollectionInfo.ID), 10 | 'symbol': item.get(SAResponseConstsCollectionInfo.SYMBOL), 11 | 'name': item.get(SAResponseConstsCollectionInfo.NAME), 12 | 'description': item.get(SAResponseConstsCollectionInfo.DESCRIPTION), 13 | 'display': parse_str_status(item.get(SAResponseConstsCollectionInfo.DISPLAY)), 14 | 'new': parse_str_status(item.get(SAResponseConstsCollectionInfo.NEW)), 15 | 'soon': parse_str_status(item.get(SAResponseConstsCollectionInfo.SOON)), 16 | 'trending': parse_str_status(item.get(SAResponseConstsCollectionInfo.TRENDING)), 17 | 'created': parse_timestamp(item.get(SAResponseConstsCollectionInfo.CREATED)), 18 | 'supply': item.get(SAResponseConstsCollectionInfo.SUPPLY), 19 | 'twitter': item.get(SAResponseConstsCollectionInfo.TWITTER), 20 | 'website': item.get(SAResponseConstsCollectionInfo.WEBSITE), 21 | 'discord': item.get(SAResponseConstsCollectionInfo.DISCORD), 22 | 'img': item.get(SAResponseConstsCollectionInfo.IMG), 23 | } 24 | 25 | 26 | def collection_list_stats_cleaner(item: dict) -> SACollectionStats: 27 | return { 28 | 'total_volume': item.get(SAResponseConstsCollectionStats.TOTAL_VOLUME), 29 | 'daily_volume': item.get(SAResponseConstsCollectionStats.DAILY_VOLUME), 30 | 'weekly_volume': item.get(SAResponseConstsCollectionStats.WEEKLY_VOLUME), 31 | 'total_sales': item.get(SAResponseConstsCollectionStats.TOTAL_SALES), 32 | 'daily_sales': item.get(SAResponseConstsCollectionStats.DAILY_SALES), 33 | 'weekly_sales': item.get(SAResponseConstsCollectionStats.WEEKLY_SALES), 34 | 'prev_daily_sales': item.get(SAResponseConstsCollectionStats.PREV_DAILY_SALES), 35 | 'prev_daily_volume': item.get(SAResponseConstsCollectionStats.PREV_DAILY_VOLUME), 36 | 'prev_weekly_sales': item.get(SAResponseConstsCollectionStats.PREV_WEEKLY_SALES), 37 | 'prev_weekly_volume': item.get(SAResponseConstsCollectionStats.PREV_WEEKLY_VOLUME), 38 | 'floor_price': item.get(SAResponseConstsCollectionStats.FLOOR_PRICE), 39 | 'owner_count': item.get(SAResponseConstsCollectionStats.OWNER_COUNT), 40 | 'symbol': item.get(SAResponseConstsCollectionStats.SYMBOL), 41 | 'last_update': parse_timestamp(item.get(SAResponseConstsCollectionStats.LAST_UPDATE)), 42 | 'category': item.get(SAResponseConstsCollectionStats.CATEGORY) 43 | } 44 | -------------------------------------------------------------------------------- /solapi/solanart/utils/types.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | 4 | class SACollectionInfo: 5 | id: int 6 | symbol: str 7 | name: str 8 | description: str 9 | display: bool 10 | new: bool 11 | soon: bool 12 | trending: bool 13 | created: date 14 | supply: int 15 | twitter: str 16 | website: str 17 | img: str 18 | discord: str 19 | 20 | 21 | class SACollectionStats: 22 | total_volume: float 23 | daily_volume: float 24 | weekly_volume: float 25 | total_sales: int 26 | daily_sales: int 27 | weekly_sales: int 28 | prev_daily_sales: int 29 | prev_daily_volume: float 30 | prev_weekly_sales: float 31 | prev_weekly_volume: float 32 | floor_price: float 33 | owner_count: int 34 | symbol: str 35 | last_update: date 36 | category: str 37 | -------------------------------------------------------------------------------- /solapi/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivannnnnnnnnn/solapi/4820f36196791c1745dc455cc33de8109dfa1376/solapi/utils/__init__.py -------------------------------------------------------------------------------- /solapi/utils/api.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from http import HTTPStatus 3 | from json.decoder import JSONDecodeError 4 | from typing import Optional, Dict, Union, List 5 | 6 | import requests 7 | 8 | import logging 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class BaseApi(abc.ABC): 14 | 15 | def _get_request(self, url: str) -> Union[Optional[Dict], Optional[List]]: 16 | r = requests.get(url) 17 | try: 18 | if r.status_code == HTTPStatus.OK: 19 | return r.json() 20 | logger.error(f'Request error. Status code: {r.status_code}') 21 | except JSONDecodeError as je: 22 | logger.error(f'Request error. Failed to parse json data. {str(je)}') 23 | return None 24 | except Exception as e: 25 | logger.error(f'Request error. {str(e)}') 26 | -------------------------------------------------------------------------------- /solapi/utils/converters.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | DIVIDER = 1000000000 4 | 5 | 6 | def convert_sol_absolute(v: Any) -> float: 7 | if isinstance(v, int): 8 | return round(v / DIVIDER, 2) 9 | return round(float(0), 2) 10 | -------------------------------------------------------------------------------- /solapi/utils/data.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | 4 | def parse_str_status(status: str): 5 | """ 6 | :param status: num in str (eg '1', '0') 7 | :return: bool 8 | """ 9 | try: 10 | return bool(int(status)) 11 | except ValueError: 12 | return False 13 | 14 | 15 | def dict_get_recursive(d: dict, key: str, separator='.'): 16 | if not isinstance(key, str): 17 | raise TypeError(f'Expected `key` as `str` instance but get {type(key)}') 18 | path = key.split(separator) 19 | assert len(path) != 0, f'invalid key: {key}' 20 | current_key = path[0] 21 | if not isinstance(d, dict): 22 | raise TypeError(f'Expected `d` as `dict` instance but get {type(d)}') 23 | if len(path) == 1: 24 | return d.get(current_key, None) 25 | return dict_get_recursive(d.get(current_key, None), separator.join(path[1:]), separator) 26 | 27 | 28 | def dict_get_recursive_safe(d: dict, key: str, separator='.'): 29 | try: 30 | return dict_get_recursive(d, key, separator) 31 | except: 32 | return None 33 | 34 | 35 | def list_map(func, collection: Optional[List]): 36 | return None if collection is None else list(map(func, collection)) 37 | -------------------------------------------------------------------------------- /solapi/utils/datetime.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | 4 | def parse_date(date: str): 5 | try: 6 | return datetime.fromisoformat(date.replace('T', ' ').replace('Z', '').split('.')[0]) 7 | except (ValueError, TypeError, AttributeError): 8 | return None 9 | 10 | 11 | def parse_timestamp(ts: int): 12 | try: 13 | return datetime.fromtimestamp(ts) 14 | except (ValueError, TypeError): 15 | return None 16 | --------------------------------------------------------------------------------