def get_token_info_from_blockchain_task(token_address: ChecksumAddress) -> bool: """ Retrieve token information from blockchain :param token_address: :return: `True` if found, `False` otherwise """ redis = get_redis() key = f"token-task:{token_address}" if result := redis.get(key): return bool(int(result))
def calculate_token_eth_price_task( token_address: ChecksumAddress, redis_key: str, force_recalculation: bool = False ) -> Optional[EthValueWithTimestamp]: """ Do price calculation for token in an async way and store it with its timestamp on redis :param token_address: Token address :param redis_key: Redis key for token price :param force_recalculation: Force a new calculation even if an old one is on cache :return: token price (in ether) when calculated """ from .services.price_service import PriceServiceProvider redis_expiration_time = 60 * 30 # Expire in 30 minutes redis = get_redis() now = timezone.now() current_timestamp = int(now.timestamp()) key_was_set = redis.set( redis_key, f"0:{current_timestamp}", ex=60 * 15, nx=True ) # Expire in 15 minutes if key_was_set or force_recalculation: price_service = PriceServiceProvider() eth_price = ( price_service.get_token_eth_value(token_address) or price_service.get_token_usd_price(token_address) / price_service.get_native_coin_usd_price() ) if not eth_price: # Try composed oracles if underlying_tokens := price_service.get_underlying_tokens(token_address): eth_price = 0 for underlying_token in underlying_tokens: # Find underlying token price and multiply by quantity address = underlying_token.address eth_price += ( calculate_token_eth_price_task( address, f"price-service:{address}:eth-price", # TODO Refactor all the calculation logic ).eth_value * underlying_token.quantity ) if eth_price: eth_value_with_timestamp = EthValueWithTimestamp(eth_price, now) redis.setex(redis_key, redis_expiration_time, str(eth_value_with_timestamp)) if not getattr(settings, "CELERY_ALWAYS_EAGER", False): # Recalculate price before cache expires and prevents recursion checking Celery Eager property calculate_token_eth_price_task.apply_async( (token_address, redis_key), {"force_recalculation": True}, countdown=redis_expiration_time - 300, ) else: logger.warning("Cannot calculate eth price for token=%s", token_address) return EthValueWithTimestamp(eth_price, now)
def test_get_token_uris(self, get_token_uris_mock: MagicMock): redis = get_redis() redis.flushall() token_uris = [ "http://testing.com/12", None, "", ] # '' will be parsed as None by the service expected_token_uris = ["http://testing.com/12", None, None] get_token_uris_mock.return_value = token_uris addresses_with_token_ids = [(Account.create().address, i) for i in range(3)] collectibles_service = CollectiblesServiceProvider() self.assertFalse(collectibles_service.cache_token_uri) self.assertEqual( collectibles_service.get_token_uris(addresses_with_token_ids), expected_token_uris, ) # Test redis cache redis_keys = redis.keys("token-uri:*") self.assertEqual(len(redis_keys), 3) # Test cache self.assertEqual(len(collectibles_service.cache_token_uri), 3) get_token_uris_mock.return_value = [] for address_with_token_id, token_uri in zip( addresses_with_token_ids, expected_token_uris ): self.assertEqual( collectibles_service.cache_token_uri[address_with_token_id], token_uri ) # Test redis cache working collectibles_service.cache_token_uri = {} self.assertEqual( collectibles_service.get_token_uris(addresses_with_token_ids), expected_token_uris, )
def setUpClass(cls) -> None: cls.price_service = PriceServiceProvider() cls.redis = get_redis()
def __new__(cls): if not hasattr(cls, "instance"): cls.instance = TransactionService(EthereumClientProvider(), get_redis()) return cls.instance
def __new__(cls): if not hasattr(cls, "instance"): cls.instance = CollectiblesService(EthereumClientProvider(), get_redis()) return cls.instance
def __init__(self, address: Optional[str], payload: Dict[str, Any]): self.redis = get_redis() self.address = address self.payload = payload self.redis_payload = self._get_redis_payload(address, payload)
def __new__(cls): if not hasattr(cls, "instance"): cls.instance = BalanceService( EthereumClientProvider(), PriceServiceProvider(), get_redis() ) return cls.instance
def test_get_collectibles(self): mainnet_node = just_test_if_mainnet_node() try: ethereum_client = EthereumClient(mainnet_node) EthereumClientProvider.instance = ethereum_client collectibles_service = CollectiblesService(ethereum_client, get_redis()) # Caches empty self.assertFalse(collectibles_service.cache_token_info) self.assertFalse(collectibles_service.cache_uri_metadata) safe_address = "0xfF501B324DC6d78dC9F983f140B9211c3EdB4dc7" ens_address = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" ens_logo_uri = "/media/tokens/logos/ENS.png" ens_token_id = 93288724337340885726942883352789513739931149355867373088241393067029827792979 dappcon_2020_address = "0x202d2f33449Bf46d6d32Ae7644aDA130876461a4" dappcon_token_id = 13 dappcon_logo_uri = Token( address=dappcon_2020_address, name="", symbol="" ).get_full_logo_uri() self.assertEqual(collectibles_service.get_collectibles(safe_address), []) erc721_addresses = [ (dappcon_2020_address, dappcon_token_id), (ens_address, ens_token_id), # ENS ] for erc721_address, token_id in erc721_addresses: ERC721TransferFactory( to=safe_address, address=erc721_address, token_id=token_id ) expected = [ Collectible( token_name="Ethereum Name Service", token_symbol="ENS", logo_uri=ens_logo_uri, address=ens_address, id=ens_token_id, uri=None, ), Collectible( token_name="DappCon2020", token_symbol="D20", logo_uri=dappcon_logo_uri, address=dappcon_2020_address, id=dappcon_token_id, uri="https://us-central1-thing-1d2be.cloudfunctions.net/getThing?thingId=Q1c8y3PwYomxjW25sW3l", ), ] collectibles = collectibles_service.get_collectibles(safe_address) self.assertEqual(len(collectibles), len(expected)) self.assertCountEqual(collectibles, expected) expected = [ CollectibleWithMetadata( token_name="Ethereum Name Service", token_symbol="ENS", logo_uri=ens_logo_uri, address=ens_address, id=93288724337340885726942883352789513739931149355867373088241393067029827792979, uri=None, metadata={ "name": "safe-multisig.eth", "description": ".eth ENS Domain", "image": collectibles_service.ENS_IMAGE_URL, }, ), CollectibleWithMetadata( token_name="DappCon2020", token_symbol="D20", logo_uri=dappcon_logo_uri, address=dappcon_2020_address, id=13, uri="https://us-central1-thing-1d2be.cloudfunctions.net/getThing?thingId=Q1c8y3PwYomxjW25sW3l", metadata={ "minted": "Minted on Mintbase.io", "image": "https://firebasestorage.googleapis.com/v0/b/thing-1d2be.appspot.com/o/token%2Fasset-1581932081565?alt=media&token=57b47904-1782-40e0-ab6d-4f8ca82e6884", "name": "Earlybird Ticket", "forSale": False, "minter": "", "external_url": "https://mintbase.io/my-market/0x202d2f33449bf46d6d32ae7644ada130876461a4", "fiatPrice": "$278.66", "tags": [], "mintedOn": {"_seconds": 1581932237, "_nanoseconds": 580000000}, "amountToMint": 10, "contractAddress": "0x202d2f33449bf46d6d32ae7644ada130876461a4", "type": "ERC721", "attributes": [ { "display_type": "date", "value": 1599516000, "trait_type": "Start Date", }, { "display_type": "date", "value": 1599688800, "trait_type": "End Date", }, { "value": "Holzmarktstraße 33, 10243 Berlin, Germany", "trait_type": "location", }, { "value": "ChIJhz8mADlOqEcR2lw7-iNCoDM", "trait_type": "place_id", }, {"value": "https://dappcon.io/", "trait_type": "website"}, ], "price": "1.1", "description": "This NFT ticket gives you full access to the 3-day conference. \nDate: 8 - 10 September *** Location: Holzmarktstraße 33 I 10243 Berlin", "numAvailable": 0, }, ), ] collectibles_with_metadata = ( collectibles_service.get_collectibles_with_metadata(safe_address) ) self.assertCountEqual(collectibles_with_metadata, expected) # Set ens trusted Token.objects.filter(address=ens_address).update(trusted=True) collectibles_with_metadata = ( collectibles_service.get_collectibles_with_metadata( safe_address, only_trusted=True ) ) self.assertCountEqual(collectibles_with_metadata, expected[:1]) # Set ens spam Token.objects.filter(address=ens_address).update(trusted=False, spam=True) collectibles_with_metadata = ( collectibles_service.get_collectibles_with_metadata( safe_address, exclude_spam=True ) ) self.assertCountEqual(collectibles_with_metadata, expected[1:]) # Caches not empty self.assertTrue(collectibles_service.cache_token_info) self.assertTrue(collectibles_service.cache_uri_metadata) finally: del EthereumClientProvider.instance