Compare commits

..

21 Commits

Author SHA1 Message Date
nolash
e52db0de35 Do not callback when none in token proofs check 2021-10-14 15:33:54 +02:00
nolash
cc58735ff1 Do not link null callbacks for token proof check 2021-10-14 15:31:58 +02:00
nolash
4b8eae41f7 Allow empty proofs in token info task 2021-10-14 12:22:41 +02:00
nolash
8d571b51c5 More deps upgrades 2021-10-14 05:28:17 +02:00
nolash
13970ec332 bump version cic-eth 2021-10-13 15:14:55 +02:00
nolash
7e6cc5714d Allow empty proof spec for tokens api 2021-10-13 15:13:43 +02:00
nolash
0f5bceb95b Bump version cic-eth 2021-10-13 14:24:29 +02:00
nolash
159fd07d63 Revert "Another attempt at config files, dockerfile change"
This reverts commit 9d3f8bf1e3.
2021-10-09 17:22:23 +02:00
nolash
9d3f8bf1e3 Another attempt at config files, dockerfile change 2021-10-09 17:12:29 +02:00
nolash
2229154c2b Add documentation 2021-10-09 16:55:05 +02:00
nolash
9637cb61f8 Add magic arg parsing for token lookup api 2021-10-09 16:21:03 +02:00
nolash
66f94ae694 Correct test default token task in unit test 2021-10-09 15:06:21 +02:00
nolash
1e65ea8511 reinstate celery seesion worker in token metadata test 2021-10-09 14:38:16 +02:00
nolash
21972e9df5 Complete token lookups with proofs 2021-10-09 14:18:10 +02:00
nolash
77fe41da4b Revert to multi-token task queue entry point for token api call 2021-10-09 13:39:33 +02:00
nolash
6098374a4e POC single token task chain for token proof lookups 2021-10-09 13:22:14 +02:00
nolash
f648d3ee68 WIP token info proof verify 2021-10-09 00:48:20 +02:00
nolash
7e2857d1e7 Add separate generic task for proof verification 2021-10-08 20:19:15 +02:00
nolash
df21db958b Move token tasks to cic_eth.eth.erc20 2021-10-08 19:21:47 +02:00
nolash
6ccffb15b6 Add trust check to token fetch in cic-eth task 2021-10-08 18:39:22 +02:00
nolash
53e9c63698 Add token api metadata getter (first without okota) 2021-10-08 16:04:13 +02:00
24 changed files with 239 additions and 92 deletions

View File

@@ -1,5 +1,4 @@
celery==4.4.7 celery==4.4.7
erc20-demurrage-token~=0.0.5a3 erc20-demurrage-token~=0.0.3a1
cic-eth-registry~=0.6.1a5 cic-eth-registry>=0.6.1a2,<0.7.0
chainlib~=0.0.9rc3 cic-eth[services]~=0.12.4a8
cic_eth~=0.12.4a9

View File

@@ -3,4 +3,3 @@ cic-eth-registry>=0.6.1a5,<0.7.0
hexathon~=0.0.1a8 hexathon~=0.0.1a8
chainqueue>=0.0.4a6,<0.1.0 chainqueue>=0.0.4a6,<0.1.0
eth-erc20>=0.1.2a2,<0.2.0 eth-erc20>=0.1.2a2,<0.2.0
chainlib-eth>=0.0.10a2,<0.1.0

View File

@@ -9,8 +9,8 @@ import semver
version = ( version = (
0, 0,
12, 12,
5, 4,
'alpha.1', 'alpha.11',
) )
version_object = semver.VersionInfo( version_object = semver.VersionInfo(

View File

@@ -1,3 +1,4 @@
celery==4.4.7 celery==4.4.7
chainlib>=0.0.10a3,<0.1.0 chainlib-eth>=0.0.9rc4,<0.1.0
semver==2.13.0 semver==2.13.0
crypto-dev-signer>=0.4.15rc2,<0.5.0

View File

@@ -13,5 +13,4 @@ erc20-faucet>=0.3.2a2,<0.4.0
erc20-transfer-authorization>=0.3.5a2,<0.4.0 erc20-transfer-authorization>=0.3.5a2,<0.4.0
sarafu-faucet>=0.0.7a2,<0.1.0 sarafu-faucet>=0.0.7a2,<0.1.0
moolb~=0.1.1b2 moolb~=0.1.1b2
chainlib-eth>=0.0.10a2,<0.1.0
okota>=0.2.4a6,<0.3.0 okota>=0.2.4a6,<0.3.0

View File

@@ -1,7 +1,7 @@
chainqueue>=0.0.6a1,<0.1.0 crypto-dev-signer>=0.4.15rc2,<=0.4.15
chainqueue>=0.0.5a1,<0.1.0
cic-eth-registry>=0.6.1a5,<0.7.0 cic-eth-registry>=0.6.1a5,<0.7.0
redis==3.5.3 redis==3.5.3
hexathon~=0.0.1a8 hexathon~=0.0.1a8
pycryptodome==3.10.1 pycryptodome==3.10.1
pyxdg==0.27 pyxdg==0.27
chainlib-eth>=0.0.10a2,<0.1.0

View File

@@ -3,6 +3,7 @@
# external imports # external imports
# local imports # local imports
from .base import Metadata
from .custom import CustomMetadata from .custom import CustomMetadata
from .person import PersonMetadata from .person import PersonMetadata
from .phone import PhonePointerMetadata from .phone import PhonePointerMetadata

View File

@@ -1,30 +1,99 @@
# standard imports # standard imports
import json
import logging import logging
import os
from typing import Dict, Union
# external imports # third-part imports
from cic_types.condiments import MetadataPointer from cic_types.models.person import generate_metadata_pointer, Person
from cic_types.ext.metadata import MetadataRequestsHandler
from cic_types.processor import generate_metadata_pointer
# local imports # local imports
from cic_ussd.cache import cache_data, get_cached_data from cic_ussd.cache import cache_data, get_cached_data
from cic_ussd.http.requests import error_handler, make_request
from cic_ussd.metadata.signer import Signer
logg = logging.getLogger(__file__) logg = logging.getLogger(__file__)
class UssdMetadataHandler(MetadataRequestsHandler): class Metadata:
def __init__(self, cic_type: MetadataPointer, identifier: bytes): """
super().__init__(cic_type, identifier) :cvar base_url: The base url or the metadata server.
:type base_url: str
"""
def cache_metadata(self, data: str): base_url = None
"""
:param data:
:type data: class MetadataRequestsHandler(Metadata):
:return:
:rtype: def __init__(self, cic_type: str, identifier: bytes, engine: str = 'pgp'):
""" """"""
cache_data(self.metadata_pointer, data) self.cic_type = cic_type
logg.debug(f'caching: {data} with key: {self.metadata_pointer}') self.engine = engine
self.headers = {
'X-CIC-AUTOMERGE': 'server',
'Content-Type': 'application/json'
}
self.identifier = identifier
self.metadata_pointer = generate_metadata_pointer(
identifier=self.identifier,
cic_type=self.cic_type
)
if self.base_url:
self.url = os.path.join(self.base_url, self.metadata_pointer)
def create(self, data: Union[Dict, str]):
""""""
data = json.dumps(data).encode('utf-8')
result = make_request(method='POST', url=self.url, data=data, headers=self.headers)
error_handler(result=result)
metadata = result.json()
return self.edit(data=metadata)
def edit(self, data: Union[Dict, str]):
""""""
cic_meta_signer = Signer()
signature = cic_meta_signer.sign_digest(data=data)
algorithm = cic_meta_signer.get_operational_key().get('algo')
formatted_data = {
'm': json.dumps(data),
's': {
'engine': self.engine,
'algo': algorithm,
'data': signature,
'digest': data.get('digest'),
}
}
formatted_data = json.dumps(formatted_data)
result = make_request(method='PUT', url=self.url, data=formatted_data, headers=self.headers)
logg.info(f'signed metadata submission status: {result.status_code}.')
error_handler(result=result)
try:
decoded_identifier = self.identifier.decode("utf-8")
except UnicodeDecodeError:
decoded_identifier = self.identifier.hex()
logg.info(f'identifier: {decoded_identifier}. metadata pointer: {self.metadata_pointer} set to: {data}.')
return result
def query(self):
""""""
result = make_request(method='GET', url=self.url)
error_handler(result=result)
result_data = result.json()
if not isinstance(result_data, dict):
raise ValueError(f'Invalid result data object: {result_data}.')
if result.status_code == 200:
if self.cic_type == ':cic.person':
person = Person()
person_data = person.deserialize(person_data=result_data)
serialized_person_data = person_data.serialize()
data = json.dumps(serialized_person_data)
else:
data = json.dumps(result_data)
cache_data(key=self.metadata_pointer, data=data)
logg.debug(f'caching: {data} with key: {self.metadata_pointer}')
return result_data
def get_cached_metadata(self): def get_cached_metadata(self):
"""""" """"""

View File

@@ -1,13 +1,12 @@
# standard imports # standard imports
# external imports # external imports
from cic_types.condiments import MetadataPointer
# local imports # local imports
from .base import UssdMetadataHandler from .base import MetadataRequestsHandler
class CustomMetadata(UssdMetadataHandler): class CustomMetadata(MetadataRequestsHandler):
def __init__(self, identifier: bytes): def __init__(self, identifier: bytes):
super().__init__(cic_type=MetadataPointer.CUSTOM, identifier=identifier) super().__init__(cic_type=':cic.custom', identifier=identifier)

View File

@@ -1,13 +1,12 @@
# standard imports # standard imports
# external imports # external imports
from cic_types.condiments import MetadataPointer
# local imports # local imports
from .base import UssdMetadataHandler from .base import MetadataRequestsHandler
class PersonMetadata(UssdMetadataHandler): class PersonMetadata(MetadataRequestsHandler):
def __init__(self, identifier: bytes): def __init__(self, identifier: bytes):
super().__init__(cic_type=MetadataPointer.PERSON, identifier=identifier) super().__init__(cic_type=':cic.person', identifier=identifier)

View File

@@ -2,13 +2,12 @@
import logging import logging
# external imports # external imports
from cic_types.condiments import MetadataPointer
# local imports # local imports
from .base import UssdMetadataHandler from .base import MetadataRequestsHandler
class PhonePointerMetadata(UssdMetadataHandler): class PhonePointerMetadata(MetadataRequestsHandler):
def __init__(self, identifier: bytes): def __init__(self, identifier: bytes):
super().__init__(cic_type=MetadataPointer.PHONE, identifier=identifier) super().__init__(cic_type=':cic.phone', identifier=identifier)

View File

@@ -1,13 +1,13 @@
# standard imports # standard imports
# external imports # external imports
from cic_types.condiments import MetadataPointer import celery
# local imports # local imports
from .base import UssdMetadataHandler from .base import MetadataRequestsHandler
class PreferencesMetadata(UssdMetadataHandler): class PreferencesMetadata(MetadataRequestsHandler):
def __init__(self, identifier: bytes): def __init__(self, identifier: bytes):
super().__init__(cic_type=MetadataPointer.PREFERENCES, identifier=identifier) super().__init__(cic_type=':cic.preferences', identifier=identifier)

View File

@@ -0,0 +1,60 @@
# standard imports
import json
import logging
from typing import Optional
from urllib.request import Request, urlopen
# third-party imports
import gnupg
# local imports
logg = logging.getLogger()
class Signer:
"""
:cvar gpg_path:
:type gpg_path:
:cvar gpg_passphrase:
:type gpg_passphrase:
:cvar key_file_path:
:type key_file_path:
"""
gpg_path: str = None
gpg_passphrase: str = None
key_file_path: str = None
def __init__(self):
self.gpg = gnupg.GPG(gnupghome=self.gpg_path)
with open(self.key_file_path, 'r') as key_file:
self.key_data = key_file.read()
def get_operational_key(self):
"""
:return:
:rtype:
"""
# import key data into keyring
self.gpg.import_keys(key_data=self.key_data)
gpg_keys = self.gpg.list_keys()
key_algorithm = gpg_keys[0].get('algo')
key_id = gpg_keys[0].get("keyid")
logg.debug(f'using signing key: {key_id}, algorithm: {key_algorithm}')
return gpg_keys[0]
def sign_digest(self, data: dict):
"""
:param data:
:type data:
:return:
:rtype:
"""
digest = data['digest']
key_id = self.get_operational_key().get('keyid')
signature = self.gpg.sign(digest, passphrase=self.gpg_passphrase, keyid=key_id)
return str(signature)

View File

@@ -1,17 +1,15 @@
# standard imports # standard imports
import json
import logging import logging
# third-party imports # third-party imports
import celery import celery
from cic_types.models.person import Person
# local imports # local imports
from cic_ussd.metadata import CustomMetadata, PersonMetadata, PhonePointerMetadata, PreferencesMetadata from cic_ussd.metadata import CustomMetadata, PersonMetadata, PhonePointerMetadata, PreferencesMetadata
from cic_ussd.tasks.base import CriticalMetadataTask from cic_ussd.tasks.base import CriticalMetadataTask
celery_app = celery.current_app celery_app = celery.current_app
logg = logging.getLogger(__file__) logg = logging.getLogger().getChild(__name__)
@celery_app.task @celery_app.task
@@ -24,13 +22,7 @@ def query_person_metadata(blockchain_address: str):
""" """
identifier = bytes.fromhex(blockchain_address) identifier = bytes.fromhex(blockchain_address)
person_metadata_client = PersonMetadata(identifier=identifier) person_metadata_client = PersonMetadata(identifier=identifier)
response = person_metadata_client.query() person_metadata_client.query()
data = response.json()
person = Person()
person_data = person.deserialize(person_data=data)
serialized_person_data = person_data.serialize()
data = json.dumps(serialized_person_data)
person_metadata_client.cache_metadata(data=data)
@celery_app.task @celery_app.task
@@ -84,9 +76,6 @@ def query_preferences_metadata(blockchain_address: str):
:type blockchain_address: str | Ox-hex :type blockchain_address: str | Ox-hex
""" """
identifier = bytes.fromhex(blockchain_address) identifier = bytes.fromhex(blockchain_address)
logg.debug(f'retrieving preferences metadata for address: {blockchain_address}.') logg.debug(f'Retrieving preferences metadata for address: {blockchain_address}.')
preferences_metadata_client = PreferencesMetadata(identifier=identifier) person_metadata_client = PreferencesMetadata(identifier=identifier)
response = preferences_metadata_client.query() return person_metadata_client.query()
data = json.dumps(response.json())
preferences_metadata_client.cache_metadata(data)
return data

View File

@@ -4,10 +4,10 @@ billiard==3.6.4.0
bcrypt==3.2.0 bcrypt==3.2.0
celery==4.4.7 celery==4.4.7
cffi==1.14.6 cffi==1.14.6
cic-eth[services]~=0.12.4a11 cic-eth[services]~=0.12.4a7
cic-notify~=0.4.0a10 cic-notify~=0.4.0a10
cic-types~=0.2.0a3 cic-types~=0.1.0a15
confini>=0.3.6rc4,<0.5.0 confini>=0.4.1a1,<0.5.0
phonenumbers==8.12.12 phonenumbers==8.12.12
psycopg2==2.8.6 psycopg2==2.8.6
python-i18n[YAML]==0.3.9 python-i18n[YAML]==0.3.9

View File

@@ -5,25 +5,24 @@ import os
# external imports # external imports
import requests_mock import requests_mock
from chainlib.hash import strip_0x from chainlib.hash import strip_0x
from cic_types.condiments import MetadataPointer
from cic_types.processor import generate_metadata_pointer from cic_types.processor import generate_metadata_pointer
# local imports # local imports
from cic_ussd.metadata.base import UssdMetadataHandler from cic_ussd.metadata.base import MetadataRequestsHandler
# external imports # external imports
def test_ussd_metadata_handler(activated_account, def test_metadata_requests_handler(activated_account,
init_cache, init_cache,
load_config, load_config,
person_metadata, person_metadata,
setup_metadata_request_handler, setup_metadata_request_handler,
setup_metadata_signer): setup_metadata_signer):
identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address)) identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
cic_type = MetadataPointer.PERSON cic_type = ':cic.person'
metadata_client = UssdMetadataHandler(cic_type, identifier) metadata_client = MetadataRequestsHandler(cic_type, identifier)
assert metadata_client.cic_type == cic_type assert metadata_client.cic_type == cic_type
assert metadata_client.engine == 'pgp' assert metadata_client.engine == 'pgp'
assert metadata_client.identifier == identifier assert metadata_client.identifier == identifier
@@ -39,5 +38,7 @@ def test_ussd_metadata_handler(activated_account,
assert result.status_code == 200 assert result.status_code == 200
person_metadata.pop('digest') person_metadata.pop('digest')
request_mocker.register_uri('GET', metadata_client.url, status_code=200, reason='OK', json=person_metadata) request_mocker.register_uri('GET', metadata_client.url, status_code=200, reason='OK', json=person_metadata)
result = metadata_client.query().json() result = metadata_client.query()
assert result == person_metadata assert result == person_metadata
cached_metadata = metadata_client.get_cached_metadata()
assert json.loads(cached_metadata) == person_metadata

View File

@@ -1,7 +1,7 @@
# standard imports # standard imports
import os import os
# external imports # external imports
from cic_types.condiments import MetadataPointer from chainlib.hash import strip_0x
from cic_types.processor import generate_metadata_pointer from cic_types.processor import generate_metadata_pointer
# local imports # local imports
@@ -11,8 +11,8 @@ from cic_ussd.metadata import CustomMetadata
def test_custom_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer): def test_custom_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer):
cic_type = MetadataPointer.CUSTOM cic_type = ':cic.custom'
identifier = bytes.fromhex(activated_account.blockchain_address) identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
custom_metadata_client = CustomMetadata(identifier) custom_metadata_client = CustomMetadata(identifier)
assert custom_metadata_client.cic_type == cic_type assert custom_metadata_client.cic_type == cic_type
assert custom_metadata_client.engine == 'pgp' assert custom_metadata_client.engine == 'pgp'

View File

@@ -1,7 +1,7 @@
# standard imports # standard imports
import os import os
# external imports # external imports
from cic_types.condiments import MetadataPointer from chainlib.hash import strip_0x
from cic_types.processor import generate_metadata_pointer from cic_types.processor import generate_metadata_pointer
# local imports # local imports
@@ -11,8 +11,8 @@ from cic_ussd.metadata import PersonMetadata
def test_person_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer): def test_person_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer):
cic_type = MetadataPointer.PERSON cic_type = ':cic.person'
identifier = bytes.fromhex(activated_account.blockchain_address) identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
person_metadata_client = PersonMetadata(identifier) person_metadata_client = PersonMetadata(identifier)
assert person_metadata_client.cic_type == cic_type assert person_metadata_client.cic_type == cic_type
assert person_metadata_client.engine == 'pgp' assert person_metadata_client.engine == 'pgp'

View File

@@ -1,7 +1,7 @@
# standard imports # standard imports
import os import os
# external imports # external imports
from cic_types.condiments import MetadataPointer from chainlib.hash import strip_0x
from cic_types.processor import generate_metadata_pointer from cic_types.processor import generate_metadata_pointer
# local imports # local imports
@@ -12,8 +12,8 @@ from cic_ussd.metadata import PhonePointerMetadata
def test_phone_pointer_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer): def test_phone_pointer_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer):
cic_type = MetadataPointer.PHONE cic_type = ':cic.phone'
identifier = bytes.fromhex(activated_account.blockchain_address) identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
phone_pointer_metadata = PhonePointerMetadata(identifier) phone_pointer_metadata = PhonePointerMetadata(identifier)
assert phone_pointer_metadata.cic_type == cic_type assert phone_pointer_metadata.cic_type == cic_type
assert phone_pointer_metadata.engine == 'pgp' assert phone_pointer_metadata.engine == 'pgp'

View File

@@ -1,7 +1,7 @@
# standard imports # standard imports
import os import os
# external imports # external imports
from cic_types.condiments import MetadataPointer from chainlib.hash import strip_0x
from cic_types.processor import generate_metadata_pointer from cic_types.processor import generate_metadata_pointer
# local imports # local imports
@@ -11,8 +11,8 @@ from cic_ussd.metadata import PreferencesMetadata
def test_preferences_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer): def test_preferences_metadata(activated_account, load_config, setup_metadata_request_handler, setup_metadata_signer):
cic_type = MetadataPointer.PREFERENCES cic_type = ':cic.preferences'
identifier = bytes.fromhex(activated_account.blockchain_address) identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
preferences_metadata_client = PreferencesMetadata(identifier) preferences_metadata_client = PreferencesMetadata(identifier)
assert preferences_metadata_client.cic_type == cic_type assert preferences_metadata_client.cic_type == cic_type
assert preferences_metadata_client.engine == 'pgp' assert preferences_metadata_client.engine == 'pgp'

View File

@@ -0,0 +1,17 @@
# standard imports
import shutil
# third-party imports
# local imports
from cic_ussd.metadata.signer import Signer
def test_client(load_config, setup_metadata_signer, person_metadata):
signer = Signer()
gpg = signer.gpg
assert signer.key_data is not None
gpg.import_keys(key_data=signer.key_data)
gpg_keys = gpg.list_keys()
assert signer.get_operational_key() == gpg_keys[0]
shutil.rmtree(Signer.gpg_path)

View File

@@ -6,19 +6,33 @@ import tempfile
# external imports # external imports
import pytest import pytest
from chainlib.hash import strip_0x from chainlib.hash import strip_0x
from cic_types.condiments import MetadataPointer
from cic_types.processor import generate_metadata_pointer from cic_types.processor import generate_metadata_pointer
# local imports # local imports
from cic_ussd.metadata import PersonMetadata, PhonePointerMetadata, PreferencesMetadata from cic_ussd.metadata import Metadata, PersonMetadata, PhonePointerMetadata, PreferencesMetadata
from cic_ussd.metadata.signer import Signer
logg = logging.getLogger(__name__) logg = logging.getLogger(__name__)
@pytest.fixture(scope='function')
def setup_metadata_signer(load_config):
temp_dir = tempfile.mkdtemp(dir='/tmp')
logg.debug(f'Created temp dir: {temp_dir}')
Signer.gpg_path = temp_dir
Signer.gpg_passphrase = load_config.get('PGP_PASSPHRASE')
Signer.key_file_path = os.path.join(load_config.get('PGP_KEYS_PATH'), load_config.get('PGP_PRIVATE_KEYS'))
@pytest.fixture(scope='function')
def setup_metadata_request_handler(load_config):
Metadata.base_url = load_config.get('CIC_META_URL')
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def account_phone_pointer(activated_account): def account_phone_pointer(activated_account):
identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address)) identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
return generate_metadata_pointer(identifier, MetadataPointer.PERSON) return generate_metadata_pointer(identifier, ':cic.phone')
@pytest.fixture(scope='function') @pytest.fixture(scope='function')

View File

@@ -10,5 +10,6 @@ erc20-transfer-authorization>=0.3.5a2,<0.4.0
erc20-faucet>=0.3.2a2,<0.4.0 erc20-faucet>=0.3.2a2,<0.4.0
sarafu-faucet>=0.0.7a2,<0.1.0 sarafu-faucet>=0.0.7a2,<0.1.0
confini>=0.4.2rc3,<1.0.0 confini>=0.4.2rc3,<1.0.0
crypto-dev-signer>=0.4.15rc2,<=0.4.15
eth-token-index>=0.2.4a1,<=0.3.0 eth-token-index>=0.2.4a1,<=0.3.0
okota>=0.2.4a5,<0.3.0 okota>=0.2.4a5,<0.3.0

View File

@@ -1,10 +1,10 @@
sarafu-faucet~=0.0.7a2 sarafu-faucet~=0.0.7a2
cic-eth[tools]~=0.12.5a1 cic-eth[tools]~=0.12.4a8
cic-types~=0.2.0a4 cic-types~=0.1.0a15
funga>=0.5.1a1,<0.6.0 crypto-dev-signer~=0.4.15a7
faker==4.17.1 faker==4.17.1
chainsyncer~=0.0.6a3 chainsyncer~=0.0.6a3
chainlib-eth~=0.0.10a2 chainlib-eth~=0.0.9a14
eth-address-index~=0.2.3a4 eth-address-index~=0.2.3a4
eth-contract-registry~=0.6.3a3 eth-contract-registry~=0.6.3a3
eth-accounts-index~=0.1.2a3 eth-accounts-index~=0.1.2a3