Compare commits
	
		
			No commits in common. "master" and "d302c5754c003230f3c469dfafb73c5647da2d49" have entirely different histories.
		
	
	
		
			master
			...
			d302c5754c
		
	
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -2,7 +2,3 @@ __pycache__
 | 
			
		||||
*.egg-info
 | 
			
		||||
build/
 | 
			
		||||
*.pyc
 | 
			
		||||
.venv
 | 
			
		||||
.clicada
 | 
			
		||||
dist/
 | 
			
		||||
.vscode/
 | 
			
		||||
@ -1,7 +1,3 @@
 | 
			
		||||
- 0.0.7
 | 
			
		||||
	* fix: make store_path relative to the users home 
 | 
			
		||||
- 0.0.6
 | 
			
		||||
	* Add cache encryption, with AES-CTR-128
 | 
			
		||||
- 0.0.5
 | 
			
		||||
	* Replace logs with colorized progress output on default loglevel
 | 
			
		||||
	* Do not repeat already failed metadata lookups
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										87
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								README.md
									
									
									
									
									
								
							@ -1,87 +0,0 @@
 | 
			
		||||
## Clicada
 | 
			
		||||
 | 
			
		||||
> Admin Command Line Interface to interact with cic-meta and cic-cache
 | 
			
		||||
 | 
			
		||||
### Pre-requisites
 | 
			
		||||
 | 
			
		||||
- Public key uploaded to `cic-auth-helper`
 | 
			
		||||
- PGP Keyring for your key
 | 
			
		||||
 | 
			
		||||
### Installation
 | 
			
		||||
 | 
			
		||||
Use either of the following installation methods:
 | 
			
		||||
 | 
			
		||||
1. Install from git release (recommended)
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
wget https://git.grassecon.net/grassrootseconomics/clicada/archive/v0.0.6.zip
 | 
			
		||||
unzip clicada-v0.0.6.zip
 | 
			
		||||
cd clicada
 | 
			
		||||
python3 -m venv .venv
 | 
			
		||||
source .venv/bin/activate
 | 
			
		||||
pip install -r requirements.txt --extra-index-url=https://pip.grassrootseconomics.net
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2. Install from pip to path (non sudo)
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip3 install -UI --extra-index-url=https://pip.grassrootseconomics.net clicada
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### GPG Keyring setup
 | 
			
		||||
 | 
			
		||||
PGP uses the default keyring, you can however pass in a custom keyring path.
 | 
			
		||||
 | 
			
		||||
To create a keyring from a specific key and get its path for `AUTH_KEYRING_PATH`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# In some dir
 | 
			
		||||
gpg --homedir .gnupg --import private.pgp
 | 
			
		||||
pwd
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Usage
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
usage: clicada [...optional arguments] [...positional arguments]
 | 
			
		||||
 | 
			
		||||
positional arguments:
 | 
			
		||||
  {user,u,tag,t}
 | 
			
		||||
    user (u)            retrieve transactions for a user
 | 
			
		||||
    tag (t)             locally assign a display value to an identifier
 | 
			
		||||
 | 
			
		||||
optional arguments:
 | 
			
		||||
  -h, --help            show this help message and exit
 | 
			
		||||
  --no-logs             Turn off all logging
 | 
			
		||||
  -v                    Be verbose
 | 
			
		||||
  -vv                   Be very verbose
 | 
			
		||||
  -c CONFIG, --config CONFIG
 | 
			
		||||
                        Configuration directory
 | 
			
		||||
  -n NAMESPACE, --namespace NAMESPACE
 | 
			
		||||
                        Configuration namespace
 | 
			
		||||
  --dumpconfig {env,ini}
 | 
			
		||||
                        Output configuration and quit. Use with --raw to omit values and output schema only.
 | 
			
		||||
  --env-prefix ENV_PREFIX
 | 
			
		||||
                        environment prefix for variables to overwrite configuration
 | 
			
		||||
  -p P, --rpc-provider P
 | 
			
		||||
                        RPC HTTP(S) provider url
 | 
			
		||||
  --rpc-dialect RPC_DIALECT
 | 
			
		||||
                        RPC HTTP(S) backend dialect
 | 
			
		||||
  --height HEIGHT       Block height to execute against
 | 
			
		||||
  -i I, --chain-spec I  Chain specification string
 | 
			
		||||
  -u, --unsafe          Do not verify address checksums
 | 
			
		||||
  --seq                 Use sequential rpc ids
 | 
			
		||||
  -y Y, --key-file Y    Keystore file to use for signing or address
 | 
			
		||||
  --raw                 Do not decode output
 | 
			
		||||
  --fee-price FEE_PRICE
 | 
			
		||||
                        override fee price
 | 
			
		||||
  --fee-limit FEE_LIMIT
 | 
			
		||||
                        override fee limit
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Example
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
AUTH_PASSPHRASE=queenmarlena AUTH_KEYRING_PATH=/home/kamikaze/grassroots/usumbufu/tests/testdata/pgp/.gnupg/ AUTH_KEY=CCE2E1D2D0E36ADE0405E2D0995BB21816313BD5 CHAIN_SPEC=evm:byzantium:8996:bloxberg CIC_REGISTRY_ADDRESS=0xcf60ebc445b636a5ab787f9e8bc465a2a3ef8299 RPC_PROVIDER=https://rpc.grassecon.net TX_CACHE_URL=https://cache.grassecon.net HTTP_CORS_ORIGIN=https://auth.grassecon.net META_HTTP_ORIGIN=https://auth.grassecon.net:443 PYTHONPATH=. python clicada/runnable/view.py u --meta-url https://auth.grassecon.net +254711000000
 | 
			
		||||
```
 | 
			
		||||
@ -1,43 +1,46 @@
 | 
			
		||||
# import notifier
 | 
			
		||||
from clicada.cli.notify import NotifyWriter
 | 
			
		||||
 | 
			
		||||
notifier = NotifyWriter()
 | 
			
		||||
# notifier.notify('loading script')
 | 
			
		||||
 | 
			
		||||
import importlib
 | 
			
		||||
import logging
 | 
			
		||||
notifier.notify('loading script')
 | 
			
		||||
 | 
			
		||||
# standard imports
 | 
			
		||||
import os
 | 
			
		||||
import logging
 | 
			
		||||
import importlib
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import chainlib.eth.cli
 | 
			
		||||
import clicada.cli.tag as cmd_tag
 | 
			
		||||
 | 
			
		||||
# local imports
 | 
			
		||||
import clicada.cli.user as cmd_user
 | 
			
		||||
 | 
			
		||||
# external imports
 | 
			
		||||
import confini
 | 
			
		||||
import chainlib.eth.cli
 | 
			
		||||
from chainlib.chain import ChainSpec
 | 
			
		||||
 | 
			
		||||
# local imports
 | 
			
		||||
import clicada.cli.user as cmd_user
 | 
			
		||||
import clicada.cli.tag as cmd_tag
 | 
			
		||||
from clicada.cli.auth import PGPAuthCrypt
 | 
			
		||||
from clicada.cli.http import HTTPSession, PGPClientSession
 | 
			
		||||
from clicada.cli.http import (
 | 
			
		||||
        HTTPSession,
 | 
			
		||||
        PGPClientSession,
 | 
			
		||||
        )
 | 
			
		||||
from clicada.crypt.aes import AESCTREncrypt
 | 
			
		||||
 | 
			
		||||
logg = logging.getLogger()
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.realpath(__file__))
 | 
			
		||||
data_dir = os.path.join(script_dir, "..", "data")
 | 
			
		||||
base_config_dir = os.path.join(data_dir, "config")
 | 
			
		||||
data_dir = os.path.join(script_dir, '..', 'data')
 | 
			
		||||
base_config_dir = os.path.join(data_dir, 'config')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullWriter:
 | 
			
		||||
 | 
			
		||||
    def notify(self, v):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def ouch(self, v):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def write(self, v):
 | 
			
		||||
        sys.stdout.write(str(v))
 | 
			
		||||
 | 
			
		||||
@ -45,12 +48,12 @@ class NullWriter:
 | 
			
		||||
class CmdCtrl:
 | 
			
		||||
 | 
			
		||||
    __cmd_alias = {
 | 
			
		||||
        "u": "user",
 | 
			
		||||
        "t": "tag",
 | 
			
		||||
            'u': 'user',
 | 
			
		||||
            't': 'tag',
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    __auth_for = [
 | 
			
		||||
        "user",
 | 
			
		||||
        'user', 
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, argv=None, description=None, logger=None, *args, **kwargs):
 | 
			
		||||
@ -69,36 +72,29 @@ class CmdCtrl:
 | 
			
		||||
        self.blockchain()
 | 
			
		||||
 | 
			
		||||
        self.remote_openers = {}
 | 
			
		||||
        if self.get("META_URL") != None:
 | 
			
		||||
        if self.get('META_URL') != None:
 | 
			
		||||
            auth_client_session = PGPClientSession(self.__auth)
 | 
			
		||||
            self.remote_openers["meta"] = HTTPSession(
 | 
			
		||||
                self.get("META_URL"),
 | 
			
		||||
                auth=auth_client_session,
 | 
			
		||||
                origin=self.config.get("META_HTTP_ORIGIN"),
 | 
			
		||||
            )
 | 
			
		||||
            self.remote_openers['meta'] = HTTPSession(self.get('META_URL'), auth=auth_client_session, origin=self.config.get('META_HTTP_ORIGIN'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def blockchain(self):
 | 
			
		||||
        self.chain_spec = ChainSpec.from_chain_str(self.config.get("CHAIN_SPEC"))
 | 
			
		||||
        self.chain_spec = ChainSpec.from_chain_str(self.config.get('CHAIN_SPEC'))
 | 
			
		||||
        self.rpc = chainlib.eth.cli.Rpc()
 | 
			
		||||
        self.__conn = self.rpc.connect_by_config(self.config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def args(self, argv):
 | 
			
		||||
        self.argparser = chainlib.eth.cli.ArgumentParser(
 | 
			
		||||
            chainlib.eth.cli.argflag_std_read
 | 
			
		||||
        )
 | 
			
		||||
        self.argparser = chainlib.eth.cli.ArgumentParser(chainlib.eth.cli.argflag_std_read)
 | 
			
		||||
        sub = self.argparser.add_subparsers()
 | 
			
		||||
        sub.dest = "command"
 | 
			
		||||
        sub_user = sub.add_parser(
 | 
			
		||||
            "user", aliases=["u"], help="retrieve transactions for a user"
 | 
			
		||||
        )
 | 
			
		||||
        sub.dest = 'command'
 | 
			
		||||
        sub_user = sub.add_parser('user', aliases=['u'], help='retrieve transactions for a user')
 | 
			
		||||
        cmd_user.process_args(sub_user)
 | 
			
		||||
        sub_tag = sub.add_parser(
 | 
			
		||||
            "tag", aliases=["t"], help="locally assign a display value to an identifier"
 | 
			
		||||
        )
 | 
			
		||||
        sub_tag = sub.add_parser('tag', aliases=['t'], help='locally assign a display value to an identifier')
 | 
			
		||||
        cmd_tag.process_args(sub_tag)
 | 
			
		||||
 | 
			
		||||
        self.cmd_args = self.argparser.parse_args(argv)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def module(self):
 | 
			
		||||
        self.cmd_string = self.cmd_args.command
 | 
			
		||||
        cmd_string_translate = self.__cmd_alias.get(self.cmd_string)
 | 
			
		||||
@ -106,12 +102,13 @@ class CmdCtrl:
 | 
			
		||||
            self.cmd_string = cmd_string_translate
 | 
			
		||||
 | 
			
		||||
        if self.cmd_string == None:
 | 
			
		||||
            self.cmd_string = "none"
 | 
			
		||||
            self.cmd_string = 'none'
 | 
			
		||||
   
 | 
			
		||||
        modname = "clicada.cli.{}".format(self.cmd_string)
 | 
			
		||||
        self.logger.debug("using module {}".format(modname))
 | 
			
		||||
        modname = 'clicada.cli.{}'.format(self.cmd_string)
 | 
			
		||||
        self.logger.debug('using module {}'.format(modname))
 | 
			
		||||
        self.cmd_mod = importlib.import_module(modname)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def logging(self, logger):
 | 
			
		||||
        self.logger = logger
 | 
			
		||||
        if self.logger == None:
 | 
			
		||||
@ -121,74 +118,69 @@ class CmdCtrl:
 | 
			
		||||
        elif self.cmd_args.v:
 | 
			
		||||
            self.logger.setLevel(logging.INFO)
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
    def config(self):
 | 
			
		||||
        override_dir = self.cmd_args.config
 | 
			
		||||
        if override_dir == None:
 | 
			
		||||
            p = os.environ.get("HOME")
 | 
			
		||||
            p = os.environ.get('HOME')
 | 
			
		||||
            if p != None:
 | 
			
		||||
                p = os.path.join(p, ".config", "cic", "clicada")
 | 
			
		||||
                p = os.path.join(p, '.config', 'cic', 'clicada')
 | 
			
		||||
                try:
 | 
			
		||||
                    os.stat(p)
 | 
			
		||||
                    override_dir = p
 | 
			
		||||
                    logg.info(
 | 
			
		||||
                        "applying user config override from standard location: {}".format(
 | 
			
		||||
                            p
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                    logg.info('applying user config override from standard location: {}'.format(p))
 | 
			
		||||
                except FileNotFoundError:
 | 
			
		||||
                    pass
 | 
			
		||||
        extra_args = self.cmd_mod.extra_args()
 | 
			
		||||
        self.config = chainlib.eth.cli.Config.from_args(
 | 
			
		||||
            self.cmd_args,
 | 
			
		||||
            base_config_dir=base_config_dir,
 | 
			
		||||
            extra_args=extra_args,
 | 
			
		||||
            default_config_dir=override_dir,
 | 
			
		||||
        )
 | 
			
		||||
        self.config = chainlib.eth.cli.Config.from_args(self.cmd_args, base_config_dir=base_config_dir, extra_args=extra_args, default_config_dir=override_dir)
 | 
			
		||||
 | 
			
		||||
        self.config.add(False, "_SEQ")
 | 
			
		||||
        self.config.add(False, '_SEQ')
 | 
			
		||||
 | 
			
		||||
        self.config.censor("AUTH_PASSPHRASE")
 | 
			
		||||
        self.config.censor('AUTH_PASSPHRASE')
 | 
			
		||||
 | 
			
		||||
        self.logger.debug('loaded config:\n{}'.format(self.config))
 | 
			
		||||
 | 
			
		||||
        self.logger.debug("loaded config:\n{}".format(self.config))
 | 
			
		||||
 | 
			
		||||
    def auth(self):
 | 
			
		||||
        typ = self.get("AUTH_TYPE")
 | 
			
		||||
        if typ != "gnupg":
 | 
			
		||||
            raise NotImplementedError("Valid aut implementations are: gnupg")
 | 
			
		||||
        typ = self.get('AUTH_TYPE')
 | 
			
		||||
        if typ != 'gnupg':
 | 
			
		||||
            raise NotImplementedError('Valid aut implementations are: gnupg')
 | 
			
		||||
        default_auth_db_path = None
 | 
			
		||||
        if os.environ.get("HOME") != None:
 | 
			
		||||
            default_auth_db_path = os.path.join(
 | 
			
		||||
                os.environ["HOME"], ".local/share/cic/clicada"
 | 
			
		||||
            )
 | 
			
		||||
        auth_db_path = self.get("AUTH_DB_PATH", default_auth_db_path)
 | 
			
		||||
        self.__auth = PGPAuthCrypt(
 | 
			
		||||
            auth_db_path, self.get("AUTH_KEY"), self.get("AUTH_KEYRING_PATH")
 | 
			
		||||
        )
 | 
			
		||||
        self.__auth.get_secret(self.get("AUTH_PASSPHRASE"))
 | 
			
		||||
        if os.environ.get('HOME') != None:
 | 
			
		||||
            default_auth_db_path = os.path.join(os.environ['HOME'], '.local/share/cic/clicada')
 | 
			
		||||
        auth_db_path = self.get('AUTH_DB_PATH', default_auth_db_path)
 | 
			
		||||
        self.__auth = PGPAuthCrypt(auth_db_path, self.get('AUTH_KEY'), self.get('AUTH_KEYRING_PATH'))
 | 
			
		||||
        self.__auth.get_secret(self.get('AUTH_PASSPHRASE'))
 | 
			
		||||
        self.encrypter = AESCTREncrypt(auth_db_path, self.__auth.secret)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get(self, k, default=None):
 | 
			
		||||
        r = self.config.get(k, default)
 | 
			
		||||
        if k in [
 | 
			
		||||
            "_FORCE",
 | 
			
		||||
                '_FORCE',
 | 
			
		||||
                ]:
 | 
			
		||||
            if r == None:
 | 
			
		||||
                return False
 | 
			
		||||
            return self.config.true(k)
 | 
			
		||||
        return r
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def chain(self):
 | 
			
		||||
        return self.chain_spec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def conn(self):
 | 
			
		||||
        return self.__conn
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        self.cmd_mod.execute(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def opener(self, k):
 | 
			
		||||
        return self.remote_openers[k]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def notifier(self):
 | 
			
		||||
        if logg.root.level >= logging.WARNING:
 | 
			
		||||
            logging.disable()
 | 
			
		||||
@ -196,12 +188,18 @@ class CmdCtrl:
 | 
			
		||||
        else:
 | 
			
		||||
            self.writer = NullWriter()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def notify(self, v):
 | 
			
		||||
        if logg.root.level <= logging.INFO:
 | 
			
		||||
            print("\033[96m" + v + "\033[0m")
 | 
			
		||||
        self.writer.notify(v)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def ouch(self, v):
 | 
			
		||||
        print("\033[91m" + v + "\033[0m")
 | 
			
		||||
        self.writer.ouch(v)
 | 
			
		||||
        print()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def write(self, v):
 | 
			
		||||
        print(v)
 | 
			
		||||
        self.writer.write("")
 | 
			
		||||
        self.writer.write(v)
 | 
			
		||||
        print()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ import hashlib
 | 
			
		||||
import urllib.parse
 | 
			
		||||
import os
 | 
			
		||||
import logging
 | 
			
		||||
from socket import getservbyname
 | 
			
		||||
 | 
			
		||||
# external imports
 | 
			
		||||
from usumbufu.client.base import (
 | 
			
		||||
@ -49,15 +48,7 @@ class HTTPSession:
 | 
			
		||||
    def __init__(self, url, auth=None, origin=None):
 | 
			
		||||
        self.base_url = url
 | 
			
		||||
        url_parts = urllib.parse.urlsplit(self.base_url)
 | 
			
		||||
        url_parts_origin_host = url_parts[1].split(":")
 | 
			
		||||
        host = url_parts_origin_host[0]
 | 
			
		||||
        try:
 | 
			
		||||
            host = host + ':' + url_parts_origin_host[1]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            host = host + ':' + str(getservbyname(url_parts[0]))
 | 
			
		||||
            logg.info('changed origin with missing port number from {} to {}'.format(url_parts[1], host))
 | 
			
		||||
        url_parts_origin = (url_parts[0], host, '', '', '',)
 | 
			
		||||
            
 | 
			
		||||
        url_parts_origin = (url_parts[0], url_parts[1], '', '', '',)
 | 
			
		||||
        self.origin = origin
 | 
			
		||||
        if self.origin == None:
 | 
			
		||||
            self.origin = urllib.parse.urlunsplit(url_parts_origin)
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,12 @@
 | 
			
		||||
# standard imports
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import shutil
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotifyWriter:
 | 
			
		||||
 | 
			
		||||
    def __init__(self, writer=sys.stdout):
 | 
			
		||||
        (c, r) = shutil.get_terminal_size()
 | 
			
		||||
        (c, r) = os.get_terminal_size()
 | 
			
		||||
        self.cols = c
 | 
			
		||||
        self.fmt = "\r{:" + "<{}".format(c) + "}"
 | 
			
		||||
        self.w = writer
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,7 @@ import json
 | 
			
		||||
 | 
			
		||||
# external imports
 | 
			
		||||
from clicada.user import FileUserStore
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
categories = [
 | 
			
		||||
    'phone',
 | 
			
		||||
@ -36,7 +35,7 @@ def validate(config, args):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def execute(ctrl):
 | 
			
		||||
    store_path = os.path.join(str(Path.home()), '.clicada')
 | 
			
		||||
    store_path = '.clicada'
 | 
			
		||||
    user_store = FileUserStore(None, ctrl.chain(), ctrl.get('_CATEGORY'), store_path, int(ctrl.get('FILESTORE_TTL')))
 | 
			
		||||
    user_store.put(ctrl.get('_IDENTIFIER'), json.dumps(ctrl.get('_TAG')), force=True)
 | 
			
		||||
    user_store.stick(ctrl.get('_IDENTIFIER'))
 | 
			
		||||
 | 
			
		||||
@ -1,24 +1,25 @@
 | 
			
		||||
# standard imports
 | 
			
		||||
import datetime
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
from chainlib.encode import TxHexNormalizer
 | 
			
		||||
from chainlib.eth.address import is_address, to_checksum_address
 | 
			
		||||
import logging
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
# external imports
 | 
			
		||||
from cic_eth_registry import CICRegistry
 | 
			
		||||
from cic_eth_registry.lookup.tokenindex import TokenIndexLookup
 | 
			
		||||
from cic_types.models.person import Person
 | 
			
		||||
from clicada.error import MetadataNotFoundError
 | 
			
		||||
from clicada.token import FileTokenStore, token_balance
 | 
			
		||||
from chainlib.eth.address import to_checksum_address
 | 
			
		||||
from chainlib.encode import TxHexNormalizer
 | 
			
		||||
from hexathon import add_0x
 | 
			
		||||
 | 
			
		||||
# local imports
 | 
			
		||||
from clicada.tx import ResolvedTokenTx, TxGetter
 | 
			
		||||
from clicada.tx import TxGetter
 | 
			
		||||
from clicada.user import FileUserStore
 | 
			
		||||
from hexathon import add_0x
 | 
			
		||||
from clicada.token import (
 | 
			
		||||
        FileTokenStore,
 | 
			
		||||
        token_balance,
 | 
			
		||||
        )
 | 
			
		||||
from clicada.tx import ResolvedTokenTx
 | 
			
		||||
from clicada.error import MetadataNotFoundError
 | 
			
		||||
 | 
			
		||||
logg = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@ -26,36 +27,24 @@ tx_normalizer = TxHexNormalizer()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def process_args(argparser):
 | 
			
		||||
    argparser.add_argument("-m", "--method", type=str, help="lookup method")
 | 
			
		||||
    argparser.add_argument(
 | 
			
		||||
        "--meta-url", dest="meta_url", type=str, help="Url to retrieve metadata from"
 | 
			
		||||
    )
 | 
			
		||||
    argparser.add_argument(
 | 
			
		||||
        "-f",
 | 
			
		||||
        "--force-update",
 | 
			
		||||
        dest="force_update",
 | 
			
		||||
        action="store_true",
 | 
			
		||||
        help="Update records of mutable entries",
 | 
			
		||||
    )
 | 
			
		||||
    argparser.add_argument(
 | 
			
		||||
        "identifier", type=str, help="user identifier (phone_number or address)"
 | 
			
		||||
    )
 | 
			
		||||
    argparser.add_argument('-m', '--method', type=str, help='lookup method')
 | 
			
		||||
    argparser.add_argument('--meta-url', dest='meta_url', type=str, help='Url to retrieve metadata from')
 | 
			
		||||
    argparser.add_argument('-f', '--force-update', dest='force_update', action='store_true', help='Update records of mutable entries')
 | 
			
		||||
    argparser.add_argument('identifier', type=str, help='user identifier')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extra_args():
 | 
			
		||||
    return {
 | 
			
		||||
        "force_update": "_FORCE",
 | 
			
		||||
        "method": "META_LOOKUP_METHOD",
 | 
			
		||||
        "meta_url": "META_URL",
 | 
			
		||||
        "identifier": "_IDENTIFIER",
 | 
			
		||||
        'force_update': '_FORCE',
 | 
			
		||||
        'method': 'META_LOOKUP_METHOD',
 | 
			
		||||
        'meta_url': 'META_URL',
 | 
			
		||||
        'identifier': '_IDENTIFIER',
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def apply_args(config, args):
 | 
			
		||||
    if config.get("META_LOOKUP_METHOD"):
 | 
			
		||||
        raise NotImplementedError(
 | 
			
		||||
            'Sorry, currently only "phone" lookup method is implemented'
 | 
			
		||||
        )
 | 
			
		||||
    if config.get('META_LOOKUP_METHOD'):
 | 
			
		||||
        raise NotImplementedError('Sorry, currently only "phone" lookup method is implemented')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate(config, args):
 | 
			
		||||
@ -63,101 +52,79 @@ def validate(config, args):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def execute(ctrl):
 | 
			
		||||
    tx_getter = TxGetter(ctrl.get("TX_CACHE_URL"), 10)
 | 
			
		||||
    tx_getter = TxGetter(ctrl.get('TX_CACHE_URL'), 10)
 | 
			
		||||
 | 
			
		||||
    store_path = os.path.join(str(Path.home()), ".clicada")
 | 
			
		||||
    user_phone_file_label = "phone"
 | 
			
		||||
    user_phone_store = FileUserStore(
 | 
			
		||||
        ctrl.opener("meta"),
 | 
			
		||||
        ctrl.chain(),
 | 
			
		||||
        user_phone_file_label,
 | 
			
		||||
        store_path,
 | 
			
		||||
        int(ctrl.get("FILESTORE_TTL")),
 | 
			
		||||
        encrypter=ctrl.encrypter,
 | 
			
		||||
    )
 | 
			
		||||
    store_path = '.clicada'
 | 
			
		||||
    user_phone_file_label = 'phone'
 | 
			
		||||
    user_phone_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_phone_file_label, store_path, int(ctrl.get('FILESTORE_TTL')), encrypter=ctrl.encrypter)
 | 
			
		||||
 | 
			
		||||
    identifier = ctrl.get("_IDENTIFIER")
 | 
			
		||||
    ctrl.notify("resolving identifier {} to wallet address".format(identifier))
 | 
			
		||||
    if is_address(identifier):
 | 
			
		||||
        user_address = identifier
 | 
			
		||||
    else:
 | 
			
		||||
        user_address = user_phone_store.by_phone(identifier, update=ctrl.get("_FORCE"))
 | 
			
		||||
    ctrl.notify('resolving identifier {} to wallet address'.format(ctrl.get('_IDENTIFIER')))
 | 
			
		||||
    user_address = user_phone_store.by_phone(ctrl.get('_IDENTIFIER'), update=ctrl.get('_FORCE'))
 | 
			
		||||
    if user_address == None:
 | 
			
		||||
        ctrl.ouch("unknown identifier: {}\n".format(identifier))
 | 
			
		||||
        ctrl.ouch('unknown identifier: {}\n'.format(ctrl.get('_IDENTIFIER')))
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
    try:
 | 
			
		||||
        user_address = to_checksum_address(user_address)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        ctrl.ouch('invalid response "{}" for {}\n'.format(user_address, identifier))
 | 
			
		||||
        ctrl.ouch('invalid response "{}" for {}\n'.format(user_address, ctrl.get('_IDENTIFIER')))
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    logg.debug("loaded user address {} for {}".format(user_address, identifier))
 | 
			
		||||
    logg.debug('loaded user address {} for {}'.format(user_address, ctrl.get('_IDENTIFIER')))
 | 
			
		||||
 | 
			
		||||
    user_address_normal = tx_normalizer.wallet_address(user_address)
 | 
			
		||||
    ctrl.notify("retrieving txs for address {}".format(user_address_normal))
 | 
			
		||||
    ctrl.notify('retrieving txs for address {}'.format(user_address_normal))
 | 
			
		||||
    txs = tx_getter.get(user_address)
 | 
			
		||||
 | 
			
		||||
    token_store = FileTokenStore(ctrl.chain(), ctrl.conn(), "token", store_path)
 | 
			
		||||
    token_store = FileTokenStore(ctrl.chain(), ctrl.conn(), 'token', store_path)
 | 
			
		||||
 | 
			
		||||
    user_address_file_label = "address"
 | 
			
		||||
    user_address_store = FileUserStore(
 | 
			
		||||
        ctrl.opener("meta"),
 | 
			
		||||
        ctrl.chain(),
 | 
			
		||||
        user_address_file_label,
 | 
			
		||||
        store_path,
 | 
			
		||||
        int(ctrl.get("FILESTORE_TTL")),
 | 
			
		||||
        encrypter=ctrl.encrypter,
 | 
			
		||||
    )
 | 
			
		||||
    user_address_file_label = 'address'
 | 
			
		||||
    user_address_store = FileUserStore(ctrl.opener('meta'), ctrl.chain(), user_address_file_label, store_path, int(ctrl.get('FILESTORE_TTL')), encrypter=ctrl.encrypter)
 | 
			
		||||
 | 
			
		||||
    r = None
 | 
			
		||||
    ctrl.write(
 | 
			
		||||
        f"""
 | 
			
		||||
Phone: {ctrl.get("_IDENTIFIER")}
 | 
			
		||||
Network address: {add_0x(user_address)}
 | 
			
		||||
Chain: {ctrl.chain().common_name()}"""
 | 
			
		||||
    )
 | 
			
		||||
    ctrl.notify("resolving metadata for address {}".format(user_address_normal))
 | 
			
		||||
    ctrl.notify('resolving metadata for address {}'.format(user_address_normal))
 | 
			
		||||
    try:
 | 
			
		||||
        r = user_address_store.by_address(
 | 
			
		||||
            user_address_normal, update=ctrl.get("_FORCE")
 | 
			
		||||
        )
 | 
			
		||||
        if r:
 | 
			
		||||
            ctrl.write(
 | 
			
		||||
                f"""
 | 
			
		||||
Name: { str(r)}
 | 
			
		||||
Registered: {datetime.datetime.fromtimestamp(r.date_registered).ctime()}
 | 
			
		||||
Gender: {r.gender}
 | 
			
		||||
Location: {r.location["area_name"]}
 | 
			
		||||
Products: {",".join(r.products)}
 | 
			
		||||
Tags: {",".join(r.tags)}"""
 | 
			
		||||
            )
 | 
			
		||||
        r = user_address_store.by_address(user_address_normal, update=ctrl.get('_FORCE'))
 | 
			
		||||
    except MetadataNotFoundError as e:
 | 
			
		||||
        ctrl.ouch(f"MetadataNotFoundError: Could not resolve metadata for user {e}\n")
 | 
			
		||||
        ctrl.ouch('could not resolve metadata for user: {}'.format(e))
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    ctrl.write("""Phone: {}
 | 
			
		||||
Network address: {}
 | 
			
		||||
Chain: {}
 | 
			
		||||
Name: {}
 | 
			
		||||
Registered: {}
 | 
			
		||||
Gender: {}
 | 
			
		||||
Location: {}
 | 
			
		||||
Products: {}
 | 
			
		||||
Tags: {}""".format(
 | 
			
		||||
    ctrl.get('_IDENTIFIER'),
 | 
			
		||||
    add_0x(user_address),
 | 
			
		||||
    ctrl.chain().common_name(),
 | 
			
		||||
    str(r),
 | 
			
		||||
    datetime.datetime.fromtimestamp(r.date_registered).ctime(),
 | 
			
		||||
    r.gender,
 | 
			
		||||
    r.location['area_name'],
 | 
			
		||||
    ','.join(r.products),
 | 
			
		||||
    ','.join(r.tags),
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
    tx_lines = []
 | 
			
		||||
    seen_tokens = {}
 | 
			
		||||
    for tx_src in txs["data"]:
 | 
			
		||||
        ctrl.notify("resolve details for tx {}".format(tx_src["tx_hash"]))
 | 
			
		||||
    for tx_src in txs['data']:
 | 
			
		||||
        ctrl.notify('resolve details for tx {}'.format(tx_src['tx_hash']))
 | 
			
		||||
        tx = ResolvedTokenTx.from_dict(tx_src)
 | 
			
		||||
        tx.resolve(
 | 
			
		||||
            token_store,
 | 
			
		||||
            user_address_store,
 | 
			
		||||
            show_decimals=True,
 | 
			
		||||
            update=ctrl.get("_FORCE"),
 | 
			
		||||
        )
 | 
			
		||||
        tx.resolve(token_store, user_address_store, show_decimals=True, update=ctrl.get('_FORCE'))
 | 
			
		||||
        tx_lines.append(tx)
 | 
			
		||||
        seen_tokens[tx.source_token_label] = tx.source_token
 | 
			
		||||
        seen_tokens[tx.destination_token_label] = tx.destination_token
 | 
			
		||||
 | 
			
		||||
    for k in seen_tokens.keys():
 | 
			
		||||
        ctrl.notify("resolve token {}".format(seen_tokens[k]))
 | 
			
		||||
        ctrl.notify('resolve token {}'.format(seen_tokens[k]))
 | 
			
		||||
        (token_symbol, token_decimals) = token_store.by_address(seen_tokens[k])
 | 
			
		||||
        ctrl.notify(
 | 
			
		||||
            "get token balance for {} => {}".format(token_symbol, seen_tokens[k])
 | 
			
		||||
        )
 | 
			
		||||
        ctrl.notify('get token balance for {} => {}'.format(token_symbol, seen_tokens[k]))
 | 
			
		||||
        balance = token_balance(ctrl.chain(), ctrl.conn(), seen_tokens[k], user_address)
 | 
			
		||||
        fmt = "{:." + str(token_decimals) + "f}"
 | 
			
		||||
        decimal_balance = fmt.format(balance / (10**token_decimals))
 | 
			
		||||
        fmt =  '{:.' + str(token_decimals) + 'f}'
 | 
			
		||||
        decimal_balance = fmt.format(balance / (10 ** token_decimals))
 | 
			
		||||
        ctrl.write("Balances:\n    {} {}".format(token_symbol, decimal_balance))
 | 
			
		||||
 | 
			
		||||
    print()
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ from cic_types.condiments import MetadataPointer
 | 
			
		||||
from cic_types.models.person import Person
 | 
			
		||||
from cic_types.ext.requests import make_request
 | 
			
		||||
from cic_types.processor import generate_metadata_pointer
 | 
			
		||||
from requests.exceptions import HTTPError
 | 
			
		||||
import requests.exceptions
 | 
			
		||||
import phonenumbers
 | 
			
		||||
 | 
			
		||||
# local imports
 | 
			
		||||
@ -222,7 +222,7 @@ class FileUserStore:
 | 
			
		||||
        try:
 | 
			
		||||
            r = getter.open(ptr)
 | 
			
		||||
            user_address = json.loads(r)
 | 
			
		||||
        except HTTPError as e:
 | 
			
		||||
        except requests.exceptions.HTTPError as e:
 | 
			
		||||
            logg.debug('no address found for phone {}: {}'.format(phone, e))
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
@ -269,7 +269,7 @@ class FileUserStore:
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logg.debug('no metadata found for {}: {}'.format(address, e))
 | 
			
		||||
        
 | 
			
		||||
        if not r:
 | 
			
		||||
        if r == None:
 | 
			
		||||
            self.failed_entities[address] = True
 | 
			
		||||
            raise MetadataNotFoundError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
usumbufu~=0.3.8
 | 
			
		||||
confini~=0.6.0
 | 
			
		||||
cic-eth-registry~=0.6.9
 | 
			
		||||
cic-types~=0.2.2
 | 
			
		||||
usumbufu~=0.3.4
 | 
			
		||||
confini~=0.5.1
 | 
			
		||||
cic-eth-registry~=0.6.1
 | 
			
		||||
cic-types~=0.2.1a8
 | 
			
		||||
phonenumbers==8.12.12
 | 
			
		||||
eth-erc20~=0.3.0
 | 
			
		||||
eth-erc20~=0.1.2
 | 
			
		||||
hexathon~=0.1.0
 | 
			
		||||
pycryptodome~=3.10.1
 | 
			
		||||
chainlib-eth~=0.1.0
 | 
			
		||||
chainlib~=0.1.0
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user