beem/cli.py
# -*- coding: utf-8 -*-
import os
import ast
import json
import sys
from prettytable import PrettyTable
from datetime import datetime, timedelta
import calendar
import pytz
import time
import hashlib
#import math currently unused module
import random
import logging
import click
from click_shell import shell
import re
from beem.instance import set_shared_blockchain_instance, shared_blockchain_instance
from beem.amount import Amount
from beem.price import Price
from beem.account import Account
from beem.steem import Steem
from beem.hive import Hive
from beem.blurt import Blurt
from beem.comment import Comment
from beem.message import Message
from beem.market import Market
from beem.block import Block
from beem.profile import Profile
from beem.wallet import Wallet
from beem.hivesigner import HiveSigner
from beem.memo import Memo
from beem.asset import Asset
from beem.witness import Witness, WitnessesRankedByVote, WitnessesVotedByAccount
from beem.blockchain import Blockchain
from beem.utils import formatTimeString, construct_authorperm, derive_beneficiaries, derive_tags, seperate_yaml_dict_from_body, derive_permlink, make_patch, create_new_password, import_coldcard_wif, generate_password, import_pubkeys, import_custom_json
from beem.vote import AccountVotes, ActiveVotes, Vote
from beem import exceptions
from beem.version import version as __version__
from beem.asciichart import AsciiChart
from beem.transactionbuilder import TransactionBuilder
from timeit import default_timer as timer
from beembase import operations
from beemgraphenebase.account import PrivateKey, PublicKey, BrainKey, PasswordKey, MnemonicKey, Mnemonic
from beemgraphenebase.base58 import Base58
from beem.nodelist import NodeList, node_answer_time
from beem.conveyor import Conveyor
from beem.imageuploader import ImageUploader
from beem.rc import RC
from beem.community import Communities, Community
from beem.blockchaininstance import BlockChainInstance
from beem.storage import get_default_config_store
click.disable_unicode_literals_warning = True
log = logging.getLogger(__name__)
availableConfigurationKeys = [
"default_account",
"default_vote_weight",
"nodes",
"password_storage",
"client_id",
"default_canonical_url",
"default_path",
"use_tor"
]
def prompt_callback(ctx, param, value):
if value in ["yes", "y", "ye"]:
value = True
else:
print("Please write yes, ye or y to confirm!")
ctx.abort()
def asset_callback(ctx, param, value):
if value not in ["STEEM", "SBD", "HIVE", "HBD", "BLURT", "TBD", "TESTS"]:
print("Please STEEM/HIVE/BLURT or SBD/HBD as asset!")
ctx.abort()
else:
return value
def prompt_flag_callback(ctx, param, value):
if not value:
ctx.abort()
def is_keyring_available():
KEYRING_AVAILABLE = False
try:
import keyring
if not isinstance(keyring.get_keyring(), keyring.backends.fail.Keyring):
KEYRING_AVAILABLE = True
else:
KEYRING_AVAILABLE = False
except ImportError:
KEYRING_AVAILABLE = False
return KEYRING_AVAILABLE
def unlock_wallet(stm, password=None, allow_wif=True):
if stm.unsigned and stm.nobroadcast:
return True
if stm.use_ledger:
return True
if not stm.wallet.locked():
return True
if not stm.wallet.store.is_encrypted():
return True
password_storage = stm.config["password_storage"]
if not password and password_storage == "keyring" and is_keyring_available():
import keyring
password = keyring.get_password("beem", "wallet")
if not password and password_storage == "environment" and "UNLOCK" in os.environ:
password = os.environ.get("UNLOCK")
if bool(password):
stm.wallet.unlock(password)
else:
if allow_wif:
password = click.prompt("Password to unlock wallet or posting/active wif", confirmation_prompt=False, hide_input=True)
else:
password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True)
if stm.wallet.is_encrypted():
try:
stm.wallet.unlock(password)
except:
try:
from beemstorage import InRamPlainKeyStore
stm.wallet.store = InRamPlainKeyStore()
stm.wallet.setKeys([password])
print("Wif accepted!")
return True
except:
if allow_wif:
raise exceptions.WrongMasterPasswordException("entered password is not a valid password/wif")
else:
raise exceptions.WrongMasterPasswordException("entered password is not a valid password")
else:
try:
stm.wallet.setKeys([password])
print("Wif accepted!")
return True
except:
try:
from beemstorage import SqliteEncryptedKeyStore
stm.wallet.store = SqliteEncryptedKeyStore(config=stm.config)
stm.wallet.unlock(password)
except:
if allow_wif:
raise exceptions.WrongMasterPasswordException("entered password is not a valid password/wif")
else:
raise exceptions.WrongMasterPasswordException("entered password is not a valid password")
if stm.wallet.locked():
if password_storage == "keyring" or password_storage == "environment":
print("Wallet could not be unlocked with %s!" % password_storage)
password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True)
if bool(password):
unlock_wallet(stm, password=password)
if not stm.wallet.locked():
return True
else:
print("Wallet could not be unlocked!")
return False
else:
print("Wallet Unlocked!")
return True
def unlock_token_wallet(stm, sc2, password=None):
if stm.unsigned and stm.nobroadcast:
return True
if stm.use_ledger:
return True
if not sc2.locked():
return True
if not sc2.store.is_encrypted():
return True
password_storage = stm.config["password_storage"]
if not password and password_storage == "keyring" and is_keyring_available():
import keyring
password = keyring.get_password("beem", "wallet")
if not password and password_storage == "environment" and "UNLOCK" in os.environ:
password = os.environ.get("UNLOCK")
if bool(password):
sc2.unlock(password)
else:
password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True)
try:
sc2.unlock(password)
except:
raise exceptions.WrongMasterPasswordException("entered password is not a valid password")
if sc2.locked():
if password_storage == "keyring" or password_storage == "environment":
print("Wallet could not be unlocked with %s!" % password_storage)
password = click.prompt("Password to unlock wallet", confirmation_prompt=False, hide_input=True)
if bool(password):
unlock_token_wallet(stm, sc2, password=password)
if not sc2.locked():
return True
else:
print("Wallet could not be unlocked!")
return False
else:
print("Wallet Unlocked!")
return True
def export_trx(tx, export):
if export is not None:
with open(export, "w", encoding="utf-8") as f:
json.dump(tx, f)
@shell(prompt='beempy> ', intro='Starting beempy... (use help to list all commands)', chain=True)
# @click.group(chain=True)
@click.option(
'--node', '-n', default="", help="URL for public Hive API (e.g. https://api.hive.blog)")
@click.option(
'--offline', '-o', is_flag=True, default=False, help="Prevent connecting to network")
@click.option(
'--no-broadcast', '-d', is_flag=True, default=False, help="Do not broadcast")
@click.option(
'--no-wallet', '-p', is_flag=True, default=False, help="Do not load the wallet")
@click.option(
'--unsigned', '-x', is_flag=True, default=False, help="Nothing will be signed, changes the default value of expires to 3600")
@click.option(
'--create-link', '-l', is_flag=True, default=False, help="Creates hivesigner links from all broadcast operations")
@click.option(
'--steem', '-s', is_flag=True, default=False, help="Connect to the Steem blockchain")
@click.option(
'--hive', '-h', is_flag=True, default=False, help="Connect to the Hive blockchain")
@click.option(
'--keys', '-k', help="JSON file that contains account keys, when set, the wallet cannot be used.")
@click.option(
'--use-ledger', '-u', is_flag=True, default=False, help="Uses the ledger device Nano S for signing.")
@click.option(
'--path', help="BIP32 path from which the keys are derived, when not set, default_path is used.")
@click.option(
'--token', '-t', is_flag=True, default=False, help="Uses a hivesigner token to broadcast (only broadcast operation with posting permission)")
@click.option(
'--expires', '-e', default=30,
help='Delay in seconds until transactions are supposed to expire(defaults to 30)')
@click.option(
'--verbose', '-v', default=3, help='Verbosity')
@click.version_option(version=__version__)
def cli(node, offline, no_broadcast, no_wallet, unsigned, create_link, steem, hive, keys, use_ledger, path, token, expires, verbose):
# Logging
log = logging.getLogger(__name__)
verbosity = ["critical", "error", "warn", "info", "debug"][int(
min(verbose, 4))]
log.setLevel(getattr(logging, verbosity.upper()))
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch = logging.StreamHandler()
ch.setLevel(getattr(logging, verbosity.upper()))
ch.setFormatter(formatter)
log.addHandler(ch)
if unsigned and expires == 30:
# Change expires to max duration when setting unsigned
expires = 3600
keys_list = []
autoconnect = False
if keys and keys != "":
if not os.path.isfile(keys):
raise Exception("File %s does not exist!" % keys)
with open(keys) as fp:
keyfile = fp.read()
if keyfile.find('\0') > 0:
with open(keys, encoding='utf-16') as fp:
keyfile = fp.read()
keyfile = ast.literal_eval(keyfile)
for account in keyfile:
for role in ["owner", "active", "posting", "memo"]:
if role in keyfile[account]:
keys_list.append(keyfile[account][role])
if len(keys_list) > 0:
autoconnect = True
if create_link:
no_broadcast = True
unsigned = True
sc2 = HiveSigner()
else:
sc2 = None
debug = verbose > 0
blurt = False
if not hive and not steem:
config = get_default_config_store()
if config["default_chain"].lower() == "hive":
hive = True
elif config["default_chain"].lower() == "steem":
steem = True
elif config["default_chain"].lower() == "blurt":
blurt = True
if hive:
stm = Hive(
node=node,
nobroadcast=no_broadcast,
keys=keys_list,
offline=offline,
nowallet=no_wallet,
unsigned=unsigned,
use_hs=token,
expiration=expires,
hivesigner=sc2,
use_ledger=use_ledger,
path=path,
debug=debug,
num_retries=10,
num_retries_call=5,
timeout=30,
autoconnect=autoconnect
)
elif steem:
stm = Steem(
node=node,
nobroadcast=no_broadcast,
offline=offline,
keys=keys_list,
nowallet=no_wallet,
unsigned=unsigned,
use_sc2=token,
expiration=expires,
steemconnect=sc2,
use_ledger=use_ledger,
path=path,
debug=debug,
num_retries=10,
num_retries_call=5,
timeout=30,
autoconnect=autoconnect
)
else:
stm = Blurt(
node=node,
nobroadcast=no_broadcast,
offline=offline,
keys=keys_list,
nowallet=no_wallet,
unsigned=unsigned,
use_sc2=token,
expiration=expires,
steemconnect=sc2,
use_ledger=use_ledger,
path=path,
debug=debug,
num_retries=10,
num_retries_call=5,
timeout=30,
autoconnect=autoconnect
)
set_shared_blockchain_instance(stm)
pass
@cli.command()
@click.argument('key')
@click.argument('value')
def set(key, value):
""" Set default_account, default_vote_weight or nodes
set [key] [value]
Examples:
Set the default vote weight to 50 %:
set default_vote_weight 50
"""
stm = shared_blockchain_instance()
if key == "default_account":
if stm.rpc is not None:
stm.rpc.rpcconnect()
stm.set_default_account(value)
elif key == "default_vote_weight":
stm.set_default_vote_weight(value)
elif key == "nodes" or key == "node":
if bool(value) or value != "default":
stm.set_default_nodes(value)
else:
stm.set_default_nodes("")
elif key == "default_chain":
stm.config["default_chain"] = value
elif key == "password_storage":
stm.config["password_storage"] = value
if is_keyring_available() and value == "keyring":
import keyring
password = click.prompt("Password to unlock wallet (Will be stored in keyring)", confirmation_prompt=False, hide_input=True)
password = keyring.set_password("beem", "wallet", password)
elif is_keyring_available() and value != "keyring":
import keyring
try:
keyring.delete_password("beem", "wallet")
except keyring.errors.PasswordDeleteError:
print("")
if value == "environment":
print("The wallet password can be stored in the UNLOCK environment variable to skip password prompt!")
elif key == "client_id":
stm.config["client_id"] = value
elif key == "hot_sign_redirect_uri":
stm.config["hot_sign_redirect_uri"] = value
elif key == "sc2_api_url":
stm.config["sc2_api_url"] = value
elif key == "hs_api_url":
stm.config["hs_api_url"] = value
elif key == "oauth_base_url":
stm.config["oauth_base_url"] = value
elif key == "default_path":
stm.config["default_path"] = value
elif key == "default_canonical_url":
stm.config["default_canonical_url"] = value
elif key == "use_condenser":
stm.config["use_condenser"] = value in ["true", "True"]
elif key == "use_tor":
stm.config["use_tor"] = value in ["true", "True"]
else:
print("wrong key")
@cli.command()
@click.option('--results', is_flag=True, default=False, help="Shows result of changing the node.")
def nextnode(results):
""" Uses the next node in list
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
stm.move_current_node_to_front()
node = stm.get_default_nodes()
offline = stm.offline
if len(node) < 2 or isinstance(node, str):
print("At least two nodes are needed!")
return
node = node[1:] + [node[0]]
if not offline:
stm.rpc.next()
stm.get_blockchain_version()
while not offline and node[0] != stm.rpc.url and len(node) > 1:
node = node[1:] + [node[0]]
stm.set_default_nodes(node)
if not results:
return
t = PrettyTable(["Key", "Value"])
t.align = "l"
if not offline:
t.add_row(["Node-Url", stm.rpc.url])
else:
t.add_row(["Node-Url", node[0]])
if not offline:
t.add_row(["Version", stm.get_blockchain_version()])
t.add_row(["HIVE", stm.is_hive])
else:
t.add_row(["Version", "beempy is in offline mode..."])
print(t)
@cli.command()
@click.option(
'--sort', '-s', is_flag=True, default=False,
help="Sort all nodes by ping value")
@click.option(
'--remove', '-r', is_flag=True, default=False,
help="Remove node with errors from list")
def pingnode(sort, remove):
""" Returns the answer time in milliseconds
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
nodes = stm.get_default_nodes()
t = PrettyTable(["Node", "Answer time [ms]"])
t.align = "l"
if sort:
sorted_node_list = []
nodelist = NodeList()
sorted_nodes = nodelist.get_node_answer_time(nodes)
for node in sorted_nodes:
t.add_row([node["url"], "%.2f" % (node["delay_ms"])])
sorted_node_list.append(node["url"])
print(t)
stm.set_default_nodes(sorted_node_list)
else:
node = stm.rpc.url
rpc_answer_time = node_answer_time(node)
rpc_time_str = "%.2f" % (rpc_answer_time * 1000)
t.add_row([node, rpc_time_str])
print(t)
@cli.command()
def about():
""" About beempy"""
print("")
print("beempy version: %s" % __version__)
print("")
print("By @holger80")
print("")
@cli.command()
@click.option(
'--version', is_flag=True, default=False,
help="Returns only the raw version value")
@click.option(
'--url', is_flag=True, default=False,
help="Returns only the raw url value")
def currentnode(version, url):
""" Sets the currently working node at the first place in the list
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
offline = stm.offline
stm.move_current_node_to_front()
node = stm.get_default_nodes()
if version and not offline:
print(stm.get_blockchain_version())
return
elif version and offline:
print("Node is offline")
return
if url and not offline:
print(stm.rpc.url)
return
t = PrettyTable(["Key", "Value"])
t.align = "l"
if not offline:
t.add_row(["Node-Url", stm.rpc.url])
else:
t.add_row(["Node-Url", node[0]])
if not offline:
t.add_row(["Version", stm.get_blockchain_version()])
t.add_row(["Chain", stm.get_blockchain_name()])
else:
t.add_row(["Version", "steempy is in offline mode..."])
print(t)
@cli.command()
@click.option(
'--show', '-s', is_flag=True, default=False,
help="Prints the updated nodes")
@click.option(
'--hive', '-h', is_flag=True, default=False,
help="Switch to HIVE blockchain, when set to true.")
@click.option(
'--steem', '-e', is_flag=True, default=False,
help="Switch to STEEM nodes, when set to true.")
@click.option(
'--blurt', '-b', is_flag=True, default=False,
help="Switch to BLURT nodes, when set to true.")
@click.option(
'--test', '-t', is_flag=True, default=False,
help="Do change the node list, only print the newest nodes setup.")
@click.option(
'--only-https', is_flag=True, default=False,
help="Use only https nodes.")
@click.option(
'--only-wss', is_flag=True, default=False,
help="Use only websocket nodes.")
def updatenodes(show, hive, steem, blurt, test, only_https, only_wss):
""" Update the nodelist from @fullnodeupdate
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if steem and hive:
print("hive and steem cannot be active both")
return
t = PrettyTable(["node", "Version", "score"])
t.align = "l"
if steem:
blockchain = "steem"
elif hive:
blockchain = "hive"
elif blurt:
blockchain = "blurt"
else:
blockchain = stm.config["default_chain"]
nodelist = NodeList()
nodelist.update_nodes(blockchain_instance=stm)
if hive:
nodes = nodelist.get_hive_nodes(wss=not only_https, https=not only_wss)
if stm.config["default_chain"] != "hive":
stm.config["default_chain"] = "hive"
elif steem:
nodes = nodelist.get_steem_nodes(wss=not only_https, https=not only_wss)
if stm.config["default_chain"] != "steem":
stm.config["default_chain"] = "steem"
elif blurt:
nodes = ["https://rpc.blurt.world", "https://blurt-rpc.steem.buzz"]
if stm.config["default_chain"] != "blurt":
stm.config["default_chain"] = "blurt"
elif stm.config["default_chain"] == "steem":
nodes = nodelist.get_steem_nodes(wss=not only_https, https=not only_wss)
elif stm.config["default_chain"] == "blurt":
nodes = ["https://rpc.blurt.world", "https://blurt-rpc.steem.buzz"]
else:
nodes = nodelist.get_hive_nodes(wss=not only_https, https=not only_wss)
if show or test:
sorted_nodes = sorted(nodelist, key=lambda node: node["score"], reverse=True)
for node in sorted_nodes:
if node["url"] in nodes:
score = float("{0:.1f}".format(node["score"]))
t.add_row([node["url"], node["version"], score])
print(t)
if not test:
stm.set_default_nodes(nodes)
stm.rpc.nodes.set_node_urls(nodes)
stm.rpc.current_rpc = 0
stm.rpc.rpcclose()
stm.rpc.rpcconnect()
@cli.command()
def config():
""" Shows local configuration
"""
stm = shared_blockchain_instance()
t = PrettyTable(["Key", "Value"])
t.align = "l"
for key in stm.config:
# hide internal config data
if key in availableConfigurationKeys and key != "nodes" and key != "node" and key != "use_tor":
t.add_row([key, stm.config[key]])
node = stm.get_default_nodes()
blockchain = stm.config["default_chain"]
nodes = json.dumps(node, indent=4)
t.add_row(["default_chain", blockchain])
t.add_row(["nodes", nodes])
if "password_storage" not in availableConfigurationKeys:
t.add_row(["password_storage", stm.config["password_storage"]])
if not stm.config["use_condenser"]:
t.add_row(["use_condenser", stm.config["use_condenser"]])
t.add_row(["data_dir", stm.config.data_dir])
t.add_row(["use_tor", bool(stm.config["use_tor"])])
print(t)
@cli.command()
@click.option('--wipe', is_flag=True, default=False,
help="Wipe old wallet without prompt.")
def createwallet(wipe):
""" Create new wallet with a new password
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if stm.wallet.created() and not wipe:
wipe_answer = click.prompt("'Do you want to wipe your wallet? Are your sure? This is IRREVERSIBLE! If you dont have a backup you may lose access to your account! [y/n]",
default="n")
if wipe_answer in ["y", "ye", "yes"]:
stm.wallet.wipe(True)
else:
return
elif wipe:
stm.wallet.wipe(True)
password = None
password = click.prompt("New wallet password", confirmation_prompt=True, hide_input=True)
if not bool(password):
print("Password cannot be empty! Quitting...")
return
password_storage = stm.config["password_storage"]
if password_storage == "keyring" and is_keyring_available():
import keyring
password = keyring.set_password("beem", "wallet", password)
elif password_storage == "environment":
print("The new wallet password can be stored in the UNLOCK environment variable to skip password prompt!")
stm.wallet.wipe(True)
stm.wallet.create(password)
set_shared_blockchain_instance(stm)
@cli.command()
@click.option('--unlock', '-u', is_flag=True, default=False, help='Unlock wallet')
@click.option('--lock', '-l', is_flag=True, default=False, help='Lock wallet')
def walletinfo(unlock, lock):
""" Show info about wallet
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if lock:
stm.wallet.lock()
elif unlock:
unlock_wallet(stm, allow_wif=False)
t = PrettyTable(["Key", "Value"])
t.align = "l"
t.add_row(["created", stm.wallet.created()])
t.add_row(["locked", stm.wallet.locked()])
t.add_row(["Number of stored keys", len(stm.wallet.getPublicKeys())])
t.add_row(["sql-file", stm.wallet.store.sqlite_file])
password_storage = stm.config["password_storage"]
t.add_row(["password_storage", password_storage])
password = os.environ.get("UNLOCK")
if password is not None:
t.add_row(["UNLOCK env set", "yes"])
else:
t.add_row(["UNLOCK env set", "no"])
if is_keyring_available():
t.add_row(["keyring installed", "yes"])
else:
t.add_row(["keyring installed", "no"])
if unlock:
if unlock_wallet(stm, allow_wif=False):
t.add_row(["Wallet unlock", "successful"])
else:
t.add_row(["Wallet unlock", "not working"])
# t.add_row(["getPublicKeys", str(stm.wallet.getPublicKeys())])
print(t)
@cli.command()
@click.option('--unsafe-import-key',
help='WIF key to parse (unsafe, unless shell history is deleted afterwards)', multiple=True)
def parsewif(unsafe_import_key):
""" Parse a WIF private key without importing
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if unsafe_import_key:
for key in unsafe_import_key:
try:
pubkey = PrivateKey(key, prefix=stm.prefix).pubkey
print(pubkey)
account = stm.wallet.getAccountFromPublicKey(str(pubkey))
account = Account(account, blockchain_instance=stm)
key_type = stm.wallet.getKeyType(account, str(pubkey))
print("Account: %s - %s" % (account["name"], key_type))
except Exception as e:
print(str(e))
else:
while True:
wifkey = click.prompt("Enter private key", confirmation_prompt=False, hide_input=True)
if not wifkey or wifkey == "quit" or wifkey == "exit":
break
try:
pubkey = PrivateKey(wifkey, prefix=stm.prefix).pubkey
print(pubkey)
account = stm.wallet.getAccountFromPublicKey(str(pubkey))
account = Account(account, blockchain_instance=stm)
key_type = stm.wallet.getKeyType(account, str(pubkey))
print("Account: %s - %s" % (account["name"], key_type))
except Exception as e:
print(str(e))
continue
@cli.command()
@click.option('--unsafe-import-key',
help='Private key to import to wallet (unsafe, unless shell history is deleted afterwards)')
def addkey(unsafe_import_key):
""" Add key to wallet
When no [OPTION] is given, a password prompt for unlocking the wallet
and a prompt for entering the private key are shown.
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm, allow_wif=False):
return
if not unsafe_import_key:
unsafe_import_key = click.prompt("Enter private key", confirmation_prompt=False, hide_input=True)
stm.wallet.addPrivateKey(unsafe_import_key)
set_shared_blockchain_instance(stm)
@cli.command()
@click.option('--confirm',
prompt='Are your sure? This is IRREVERSIBLE! If you dont have a backup you may lose access to your account!',
hide_input=False, callback=prompt_flag_callback, is_flag=True,
confirmation_prompt=False, help='Please confirm!')
@click.argument('pub')
def delkey(confirm, pub):
""" Delete key from the wallet
PUB is the public key from the private key
which will be deleted from the wallet
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm, allow_wif=False):
return
stm.wallet.removePrivateKeyFromPublicKey(pub)
set_shared_blockchain_instance(stm)
@cli.command()
@click.option('--import-word-list', '-l', help='Imports a BIP39 wordlist and derives a private and public key', is_flag=True, default=False)
@click.option('--strength', help='Defines word list length for BIP39 (default = 256).', default=256)
@click.option('--passphrase', '-p', help='Sets a BIP39 passphrase', is_flag=True, default=False)
@click.option('--path', '-m', help='Sets a path for BIP39 key creations. When path is set, network, role, account_keys, account and sequence is not used')
@click.option('--network', '-n', help='Network index, when using BIP39, 0 for steem and 13 for hive, (default is 13)', default=13)
@click.option('--role', '-r', help='Defines which key role should be created (default = owner).', default="owner")
@click.option('--account-keys', '-k', help='Derives four BIP39 keys for each role', is_flag=True, default=False)
@click.option('--sequence', '-s', help='Sequence key number, when using BIP39 (default is 0)', default=0)
@click.option('--account', '-a', help='sequence number for BIP39 key, default = 0')
@click.option('--wif', '-w', help='Defines how many times the password is replaced by its WIF representation for password based keys (default = 0).')
@click.option('--export-pub', '-u', help='Exports the public account keys to a json file for account creation or keychange')
@click.option('--export', '-e', help='The results are stored in a text file and will not be shown')
def keygen(import_word_list, strength, passphrase, path, network, role, account_keys, sequence, account, wif, export_pub, export):
""" Creates a new random BIP39 key and prints its derived private key and public key.
The generated key is not stored. Can also be used to create new keys for an account.
Can also be used to derive account keys from a password or BIP39 wordlist.
"""
stm = shared_blockchain_instance()
pub_json = {"owner": "", "active": "", "posting": "", "memo": ""}
if not account_keys and len(role.split(",")) > 1:
roles = role.split(",")
account_keys = True
else:
roles = ['owner', 'active', 'posting', 'memo']
if wif is not None:
wif = int(wif)
else:
wif = 0
if stm.use_ledger:
if stm.rpc is not None:
stm.rpc.rpcconnect()
ledgertx = stm.new_tx()
ledgertx.constructTx()
if account is None:
account = 0
else:
account = int(account)
t = PrettyTable(["Key", "Value"])
t_pub = PrettyTable(["Key", "Value"])
t.align = "l"
t_pub.align = "l"
t.add_row(["Account sequence", account])
t.add_row(["Key sequence", sequence])
if account_keys and path is None:
for r in roles:
path = ledgertx.ledgertx.build_path(r, account, sequence)
pubkey = ledgertx.ledgertx.get_pubkey(path, request_screen_approval=False)
aprove_key = PrettyTable(["Approve %s Key" % r])
aprove_key.align = "l"
aprove_key.add_row([format(pubkey, "STM")])
print(aprove_key)
ledgertx.ledgertx.get_pubkey(path, request_screen_approval=True)
t_pub.add_row(["%s Public Key" % r, format(pubkey, "STM")])
t.add_row(["%s path" % r, path])
pub_json[r] = format(pubkey, "STM")
else:
if path is None:
path = ledgertx.ledgertx.build_path(role, account, sequence)
t.add_row(["Key role", role])
t.add_row(["path", path])
pubkey = ledgertx.ledgertx.get_pubkey(path, request_screen_approval=False)
aprove_key = PrettyTable(["Approve %s Key" % role])
aprove_key.align = "l"
aprove_key.add_row([format(pubkey, "STM")])
print(aprove_key)
ledgertx.ledgertx.get_pubkey(path, request_screen_approval=True)
t_pub.add_row(["Public Key", format(pubkey, "STM")])
pub_json[role] = format(pubkey, "STM")
else:
if account is None:
account = 0
else:
account = int(account)
if import_word_list:
n_words = click.prompt("Enter word list length or complete word list")
if len(n_words.split(" ")) > 0:
word_list = n_words
else:
n_words = int(n_words)
word_array = []
word = None
m = Mnemonic()
while len(word_array) < n_words:
word = click.prompt("Enter %d. mnemnoric word" % (len(word_array) + 1), type=str)
word = m.expand_word(word)
if m.check_word(word):
word_array.append(word)
print(" ".join(word_array))
word_list = " ".join(word_array)
if passphrase:
passphrase = click.prompt("Enter passphrase", confirmation_prompt=True, hide_input=True)
else:
passphrase = ""
mk = MnemonicKey(word_list=word_list, passphrase=passphrase, account_sequence=account, key_sequence=sequence)
if path is not None:
mk.set_path(path)
else:
mk.set_path_BIP48(network_index=network, role=role, account_sequence=account, key_sequence=sequence)
else:
mk = MnemonicKey(account_sequence=account, key_sequence=sequence)
if path is not None:
mk.set_path(path)
else:
mk.set_path_BIP48(network_index=network, role=role, account_sequence=account, key_sequence=sequence)
if passphrase:
passphrase = click.prompt("Enter passphrase", confirmation_prompt=True, hide_input=True)
else:
passphrase = ""
word_list = mk.generate_mnemonic(passphrase=passphrase, strength=strength)
t = PrettyTable(["Key", "Value"])
t_pub = PrettyTable(["Key", "Value"])
t.align = "l"
t_pub.align = "l"
t.add_row(["Account sequence", account])
t.add_row(["Key sequence", sequence])
if account_keys and path is None:
for r in roles:
t.add_row(["%s Private Key" % r, str(mk.get_private())])
mk.set_path_BIP48(network_index=network, role=r, account_sequence=account, key_sequence=sequence)
t_pub.add_row(["%s Public Key" % r, format(mk.get_public(), "STM")])
t.add_row(["%s path" % r, mk.get_path()])
pub_json[r] = format(mk.get_public(), "STM")
if passphrase != "":
t.add_row(["Passphrase", passphrase])
t.add_row(["BIP39 wordlist", word_list])
else:
t.add_row(["Key role", role])
t.add_row(["path", mk.get_path()])
t.add_row(["BIP39 wordlist", word_list.lower()])
if passphrase != "":
t.add_row(["Passphrase", passphrase])
t.add_row(["Private Key", str(mk.get_private())])
t_pub.add_row(["Public Key", format(mk.get_public(), "STM")])
pub_json[role] = format(mk.get_public(), "STM")
if export_pub and export_pub != "":
pub_json = json.dumps(pub_json, indent=4)
with open(export_pub, 'w') as fp:
fp.write(pub_json)
print("%s was sucessfully saved." % export_pub)
if export and export != "":
with open(export, 'w') as fp:
fp.write(str(t))
fp.write("\n")
fp.write(str(t_pub))
print("%s was sucessfully saved." % export)
else:
print(t_pub)
print(t)
@cli.command()
@click.option('--role', '-r', help='Defines which key role should be created. When owner is not set as role and an cold card wif is imported, the Master Password is not shown. (default = owner,active,posting,memo when creating account keys).', default="owner,active,posting,memo")
@click.option('--account', '-a', help='account name for password based key generation')
@click.option('--import-password', '-i', help='Imports a password and derives all four account keys', is_flag=True, default=False)
@click.option('--import-coldcard', '-o', help='Text file with a BIP85 WIF generated by a coldcard. The imported WIF is used to derives all four account keys')
@click.option('--wif', '-w', help='Defines how many times the password is replaced by its WIF representation for password based keys (default = 0 or 1 when importing a cold card wif).')
@click.option('--export-pub', '-u', help='Exports the public account keys to a json file for account creation or keychange')
@click.option('--export', '-e', help='The results are stored in a text file and will not be shown')
def passwordgen(role, account, import_password, import_coldcard, wif, export_pub, export):
""" Creates a new password based key and prints its derived private key and public key.
The generated key is not stored. The password is used to create new keys for an account.
"""
stm = shared_blockchain_instance()
if not account:
account = stm.config["default_account"]
if import_password:
import_password = click.prompt("Enter password", confirmation_prompt=False, hide_input=True)
elif import_coldcard is not None:
import_password, path = import_coldcard_wif(import_coldcard)
else:
import_password = create_new_password(length=32)
pub_json = {"owner": "", "active": "", "posting": "", "memo": ""}
if len(role.split(",")) > 1:
roles = role.split(",")
elif role in ['owner', 'active', 'posting', 'memo']:
roles = [role]
else:
roles = ['owner', 'active', 'posting', 'memo']
if wif is not None:
wif = int(wif)
elif import_coldcard:
wif = 1
else:
wif = 0
password = generate_password(import_password, wif)
t = PrettyTable(["Key", "Value"])
t_pub = PrettyTable(["Key", "Value"])
t.add_row(["Username", account])
t_pub.add_row(["Username", account])
if import_coldcard:
t_pub.add_row(["cold card path", path])
t.align = "l"
t_pub.align = "l"
for r in roles:
pk = PasswordKey(account, password, role=r)
t.add_row(["%s Private Key" % r, str(pk.get_private())])
t_pub.add_row(["%s Public Key" % r, format(pk.get_public(), "STM")])
pub_json[r] = format(pk.get_public(), "STM")
if "owner" in roles or import_coldcard is None:
t.add_row(["Backup (Master) Password", password])
if wif > 0:
t.add_row(["WIF itersions", wif])
if "owner" in roles or import_coldcard is None:
t.add_row(["Entered/created Password", import_password])
if export_pub and export_pub != "":
pub_json = json.dumps(pub_json, indent=4)
with open(export_pub, 'w') as fp:
fp.write(pub_json)
print("%s was sucessfully saved." % export_pub)
if export and export != "":
with open(export, 'w') as fp:
fp.write(str(t))
fp.write("\n")
fp.write(str(t_pub))
print("%s was sucessfully saved." % export)
else:
print(t_pub)
print(t)
@cli.command()
@click.argument('name')
@click.option('--unsafe-import-token',
help='Private key to import to wallet (unsafe, unless shell history is deleted afterwards)')
def addtoken(name, unsafe_import_token):
""" Add key to wallet
When no [OPTION] is given, a password prompt for unlocking the wallet
and a prompt for entering the private key are shown.
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
sc2 = HiveSigner(blockchain_instance=stm)
if not unlock_token_wallet(stm, sc2):
return
if not unsafe_import_token:
unsafe_import_token = click.prompt("Enter private token", confirmation_prompt=False, hide_input=True)
sc2.addToken(name, unsafe_import_token)
set_shared_blockchain_instance(stm)
@cli.command()
@click.option('--confirm',
prompt='Are your sure?',
hide_input=False, callback=prompt_flag_callback, is_flag=True,
confirmation_prompt=False, help='Please confirm!')
@click.argument('name')
def deltoken(confirm, name):
""" Delete name from the wallet
name is the public name from the private token
which will be deleted from the wallet
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
sc2 = HiveSigner(blockchain_instance=stm)
if not unlock_token_wallet(stm, sc2):
return
sc2.removeTokenFromPublicName(name)
set_shared_blockchain_instance(stm)
@cli.command()
@click.option('--path', '-p', help='Set path (when using ledger)')
@click.option('--ledger-approval', '-a', is_flag=True, default=False, help='When set, you can confirm the shown pubkey on your ledger.')
def listkeys(path, ledger_approval):
""" Show stored keys
Can be used to receive and approve the pubkey obtained from the ledger
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if stm.use_ledger:
if path is None:
path = stm.config["default_path"]
t = PrettyTable(["Available Key for %s" % path])
t.align = "l"
ledgertx = stm.new_tx()
ledgertx.constructTx()
pubkey = ledgertx.ledgertx.get_pubkey(path, request_screen_approval=False)
t.add_row([str(pubkey)])
if ledger_approval:
print(t)
ledgertx.ledgertx.get_pubkey(path, request_screen_approval=True)
else:
t = PrettyTable(["Available Key"])
t.align = "l"
for key in stm.wallet.getPublicKeys():
t.add_row([key])
print(t)
@cli.command()
def listtoken():
""" Show stored token
"""
stm = shared_blockchain_instance()
t = PrettyTable(["name", "scope", "status"])
t.align = "l"
sc2 = HiveSigner(blockchain_instance=stm)
if not unlock_token_wallet(stm, sc2):
return
for name in sc2.getPublicNames():
ret = sc2.me(username=name)
if "error" in ret:
t.add_row([name, "-", ret["error"]])
else:
t.add_row([name, ret["scope"], "ok"])
print(t)
@cli.command()
@click.option('--role', '-r', help='When set, limits the shown keys for this role')
@click.option('--max-account-index', '-a', help='Set maximum account index to check pubkeys (only when using ledger)', default=5)
@click.option('--max-sequence', '-s', help='Set maximum key sequence to check pubkeys (only when using ledger)', default=2)
def listaccounts(role, max_account_index, max_sequence):
"""Show stored accounts
Can be used with the ledger to obtain all accounts that uses pubkeys derived from this ledger
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if stm.use_ledger:
t = PrettyTable(["Name", "Type", "Available Key", "Path"])
t.align = "l"
ledgertx = stm.new_tx()
ledgertx.constructTx()
key_found = False
path = None
current_account_index = 0
current_key_index = 0
role_list = ["owner", "active", "posting", "memo"]
if role:
role_list = [role]
while not key_found and current_account_index < max_account_index:
for perm in role_list:
path = ledgertx.ledgertx.build_path(perm, current_account_index, current_key_index)
current_pubkey = ledgertx.ledgertx.get_pubkey(path)
account = stm.wallet.getAccountFromPublicKey(str(current_pubkey))
if account is not None:
t.add_row([str(account), perm, str(current_pubkey), path])
if current_key_index < max_sequence:
current_key_index += 1
else:
current_key_index = 0
current_account_index += 1
else:
t = PrettyTable(["Name", "Type", "Available Key"])
t.align = "l"
for account in stm.wallet.getAccounts():
t.add_row([
account["name"] or "n/a", account["type"] or "n/a",
account["pubkey"]
])
print(t)
@cli.command()
@click.argument('post', nargs=1)
@click.option('--weight', '-w', help='Vote weight (from 0.1 to 100.0)')
@click.option('--account', '-a', help='Voter account name')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def upvote(post, account, weight, export):
"""Upvote a post/comment
POST is @author/permlink
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not weight:
weight = stm.config["default_vote_weight"]
else:
weight = float(weight)
if weight > 100:
raise ValueError("Maximum vote weight is 100.0!")
elif weight < 0:
raise ValueError("Minimum vote weight is 0!")
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
try:
post = Comment(post, blockchain_instance=stm)
tx = post.upvote(weight, voter=account)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
except exceptions.VotingInvalidOnArchivedPost:
print("Post/Comment is older than 7 days! Did not upvote.")
tx = {}
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('post', nargs=1)
@click.option('--account', '-a', help='Account name')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def delete(post, account, export):
"""delete a post/comment
POST is @author/permlink
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
try:
post = Comment(post, blockchain_instance=stm)
tx = post.delete(account=account)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
except exceptions.VotingInvalidOnArchivedPost:
print("Could not delete post.")
tx = {}
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('post', nargs=1)
@click.option('--account', '-a', help='Downvoter account name')
@click.option('--weight', '-w', default=100, help='Downvote weight (from 0.1 to 100.0)')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def downvote(post, account, weight, export):
"""Downvote a post/comment
POST is @author/permlink
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
weight = float(weight)
if weight > 100:
raise ValueError("Maximum downvote weight is 100.0!")
elif weight < 0:
raise ValueError("Minimum downvote weight is 0!")
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
try:
post = Comment(post, blockchain_instance=stm)
tx = post.downvote(weight, voter=account)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
except exceptions.VotingInvalidOnArchivedPost:
print("Post/Comment is older than 7 days! Did not downvote.")
tx = {}
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('to', nargs=1)
@click.argument('amount', nargs=1)
@click.argument('asset', nargs=1, callback=asset_callback)
@click.argument('memo', nargs=1, required=False)
@click.option('--account', '-a', help='Transfer from this account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def transfer(to, amount, asset, memo, account, export):
"""Transfer SBD/HBD or STEEM/HIVE"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not bool(memo):
memo = ''
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.transfer(to, amount, asset, memo)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('amount', nargs=1)
@click.option('--account', '-a', help='Powerup from this account')
@click.option('--to', '-t', help='Powerup this account', default=None)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def powerup(amount, account, to, export):
"""Power up (vest STEEM/HIVE as STEEM/HIVE POWER)"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
try:
amount = float(amount)
except:
amount = str(amount)
tx = acc.transfer_to_vesting(amount, to=to)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('amount', nargs=1)
@click.option('--account', '-a', help='Powerup from this account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def powerdown(amount, account, export):
"""Power down (start withdrawing VESTS from Steem POWER)
amount is in VESTS
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
try:
amount = float(amount)
except:
amount = str(amount)
tx = acc.withdraw_vesting(amount)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('amount', nargs=1)
@click.argument('to_account', nargs=1)
@click.option('--account', '-a', help='Delegate from this account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def delegate(amount, to_account, account, export):
"""Delegate (start delegating VESTS to another account)
amount is in VESTS / Steem
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
try:
amount = float(amount)
except:
amount = Amount(str(amount), blockchain_instance=stm)
if amount.symbol == stm.token_symbol and isinstance(stm, Steem):
amount = stm.sp_to_vests(float(amount))
elif amount.symbol == stm.token_symbol and isinstance(stm, Hive):
amount = stm.hp_to_vests(float(amount))
tx = acc.delegate_vesting_shares(to_account, amount)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.option('--account', '-a', help="List outgoing delegations from this account")
def listdelegations(account):
""" List all outgoing delegations from an account.
The default account is used if no other account name is given as
option to this command.
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
acc = Account(account, blockchain_instance=stm)
pt = PrettyTable(["Delegatee", stm.vests_symbol, "%s Power" % (stm.token_symbol)])
pt.align = "r"
start_account = ""
limit = 100
stop = False
while stop is False:
delegations = acc.get_vesting_delegations(
start_account=start_account, limit=limit)
if len(delegations) < limit:
stop = True
if start_account != "" and len(delegations) > 0:
# skip first entry if it was already part of the previous call
delegations = delegations[1:]
for deleg in delegations:
vests = Amount(deleg['vesting_shares'], blockchain_instance=stm)
token_power = "%.3f" % (stm.vests_to_token_power(vests))
pt.add_row([deleg['delegatee'], str(vests), token_power])
start_account = deleg['delegatee']
print(pt)
@cli.command()
@click.argument('to', nargs=1)
@click.option('--percentage', default=100, help='The percent of the withdraw to go to the "to" account')
@click.option('--account', '-a', help='Powerup from this account')
@click.option('--auto_vest', help='Set to true if the from account should receive the VESTS as'
'VESTS, or false if it should receive them as STEEM/HIVE.', is_flag=True)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def powerdownroute(to, percentage, account, auto_vest, export):
"""Setup a powerdown route"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.set_withdraw_vesting_route(to, percentage, auto_vest=auto_vest)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('new_recovery_account', nargs=1)
@click.option('--account', '-a', help='Change the recovery account from this account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def changerecovery(new_recovery_account, account, export):
"""Changes the recovery account with the owner key (needs 30 days to be active)"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
#if not unlock_wallet(stm):
# return
new_recovery_account = Account(new_recovery_account, blockchain_instance=stm)
account = Account(account, blockchain_instance=stm)
op = operations.Change_recovery_account(**{
'account_to_recover': account['name'],
'new_recovery_account': new_recovery_account['name'],
'extensions': []
})
tb = TransactionBuilder(blockchain_instance=stm)
tb.appendOps([op])
if stm.unsigned:
tb.addSigningInformation(account["name"], "owner")
tx = tb
else:
key = click.prompt('Owner key for %s' % account["name"], confirmation_prompt=False, hide_input=True)
owner_key = PrivateKey(wif=key)
tb.appendWif(str(owner_key))
tb.sign()
tx = tb.broadcast()
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('amount', nargs=1)
@click.option('--account', '-a', help='Powerup from this account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def convert(amount, account, export):
"""Convert SBD/HBD to Steem/Hive (takes a week to settle)"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
try:
amount = float(amount)
except:
amount = str(amount)
tx = acc.convert(amount)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
def changewalletpassphrase():
""" Change wallet password
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm, allow_wif=False):
return
newpassword = None
newpassword = click.prompt("New wallet password", confirmation_prompt=True, hide_input=True)
if not bool(newpassword):
print("Password cannot be empty! Quitting...")
return
password_storage = stm.config["password_storage"]
if password_storage == "keyring" and is_keyring_available():
import keyring
keyring.set_password("beem", "wallet", newpassword)
elif password_storage == "environment":
print("The new wallet password can be stored in the UNLOCK invironment variable to skip password prompt!")
stm.wallet.changePassphrase(newpassword)
@cli.command()
@click.argument('account', nargs=-1)
def power(account):
""" Shows vote power and bandwidth
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if len(account) == 0:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
for name in account:
a = Account(name, blockchain_instance=stm)
print("\n@%s" % a.name)
a.print_info(use_table=True)
@cli.command()
@click.argument('account', nargs=-1)
def balance(account):
""" Shows balance
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if len(account) == 0:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
for name in account:
a = Account(name, blockchain_instance=stm)
print("\n@%s" % a.name)
t = PrettyTable(["Account", stm.token_symbol, stm.backed_token_symbol, "VESTS"])
t.align = "r"
t.add_row([
'Available',
str(a.balances['available'][0]),
str(a.balances['available'][1]),
str(a.balances['available'][2]),
])
t.add_row([
'Rewards',
str(a.balances['rewards'][0]),
str(a.balances['rewards'][1]),
str(a.balances['rewards'][2]),
])
t.add_row([
'Savings',
str(a.balances['savings'][0]),
str(a.balances['savings'][1]),
'N/A',
])
t.add_row([
'TOTAL',
str(a.balances['total'][0]),
str(a.balances['total'][1]),
str(a.balances['total'][2]),
])
print(t)
@cli.command()
@click.argument('account', nargs=-1, required=False)
def interest(account):
""" Get information about interest payment
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
t = PrettyTable([
"Account", "Last Interest Payment", "Next Payment",
"Interest rate", "Interest"
])
t.align = "r"
for a in account:
a = Account(a, blockchain_instance=stm)
i = a.interest()
t.add_row([
a["name"],
i["last_payment"],
"in %s" % (i["next_payment_duration"]),
"%.1f%%" % i["interest_rate"],
"%.3f %s" % (i["interest"], stm.backed_token_symbol),
])
print(t)
@cli.command()
@click.argument('follow-type')
@click.option('--account', '-a', help='Get follow list for this account')
@click.option('--limit', '-l', help='Liimts the returned accounts', default=100)
def followlist(follow_type, account, limit):
""" Get information about followed lists
follow_type can be blog
On Hive, follow type can also be one the following: blacklisted, follow_blacklist, muted, or follow_muted
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
account = Account(account, blockchain_instance=stm)
if follow_type == "blog":
name_list = account.get_following(limit=limit)
else:
name_list = account.get_follow_list(follow_type, limit=limit)
t = PrettyTable(["index", "name"])
t.align = "r"
i = 0
for name in name_list:
i += 1
t.add_row([str(i), name])
print(t)
@cli.command()
@click.argument('account', nargs=-1, required=False)
def follower(account):
""" Get information about followers
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
for a in account:
a = Account(a, blockchain_instance=stm)
print("\nFollowers statistics for @%s (please wait...)" % a.name)
followers = a.get_followers(False)
followers.print_summarize_table(tag_type="Followers")
@cli.command()
@click.argument('account', nargs=-1, required=False)
def following(account):
""" Get information about following
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
for a in account:
a = Account(a, blockchain_instance=stm)
print("\nFollowing statistics for @%s (please wait...)" % a.name)
following = a.get_following(False)
following.print_summarize_table(tag_type="Following")
@cli.command()
@click.argument('account', nargs=-1, required=False)
def muter(account):
""" Get information about muter
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
for a in account:
a = Account(a, blockchain_instance=stm)
print("\nMuters statistics for @%s (please wait...)" % a.name)
muters = a.get_muters(False)
muters.print_summarize_table(tag_type="Muters")
@cli.command()
@click.argument('account', nargs=-1, required=False)
def muting(account):
""" Get information about muting
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = [stm.config["default_account"]]
for a in account:
a = Account(a, blockchain_instance=stm)
print("\nMuting statistics for @%s (please wait...)" % a.name)
muting = a.get_mutings(False)
muting.print_summarize_table(tag_type="Muting")
@cli.command()
@click.argument('account', nargs=1, required=False)
@click.option('--limit', '-l', help='Limits shown notifications')
@click.option('--all', '-a', help='Show all notifications (when not set, only unread are shown)', is_flag=True, default=False)
@click.option('--mark_as_read', '-m', help='Broadcast a mark all as read custom json', is_flag=True, default=False)
@click.option('--replies', '-r', help='Show only replies', is_flag=True, default=False)
@click.option('--mentions', '-t', help='Show only mentions', is_flag=True, default=False)
@click.option('--follows', '-f', help='Show only follows', is_flag=True, default=False)
@click.option('--votes', '-v', help='Show only upvotes', is_flag=True, default=False)
@click.option('--reblogs', '-b', help='Show only reblogs', is_flag=True, default=False)
@click.option('--reverse', '-s', help='Reverse sorting of notifications', is_flag=True, default=False)
def notifications(account, limit, all, mark_as_read, replies, mentions, follows, votes, reblogs, reverse):
""" Show notifications of an account
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if account is None or account == "":
if "default_account" in stm.config:
account = stm.config["default_account"]
if mark_as_read and not unlock_wallet(stm):
return
if not replies and not mentions and not follows and not votes and not reblogs:
show_all = True
else:
show_all = False
account = Account(account, blockchain_instance=stm)
t = PrettyTable(["Date", "Type", "Message"], hrules=0)
t.align = "r"
last_read = None
if limit is not None:
limit = int(limit)
all_notifications = account.get_notifications(only_unread=not all, limit=limit)
if reverse:
all_notifications = all_notifications[::-1]
for note in all_notifications:
if not show_all:
if note["type"] == "reblog" and not reblogs:
continue
elif note["type"] == "reply" and not replies:
continue
elif note["type"] == "reply_comment" and not replies:
continue
elif note["type"] == "mention" and not mentions:
continue
elif note["type"] == "follow" and not follows:
continue
elif note["type"] == "vote" and not votes:
continue
t.add_row([
str(datetime.fromtimestamp(calendar.timegm(note["date"].timetuple()))),
note["type"],
note["msg"],
])
last_read = note["date"]
print(t)
if mark_as_read:
account.mark_notifications_as_read(last_read=last_read)
@cli.command()
@click.argument('account', nargs=1, required=False)
def permissions(account):
""" Show permissions of an account
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = stm.config["default_account"]
account = Account(account, blockchain_instance=stm)
t = PrettyTable(["Permission", "Threshold", "Key/Account"], hrules=0)
t.align = "r"
for permission in ["owner", "active", "posting"]:
auths = []
for type_ in ["account_auths", "key_auths"]:
for authority in account[permission][type_]:
auths.append("%s (%d)" % (authority[0], authority[1]))
t.add_row([
permission,
account[permission]["weight_threshold"],
"\n".join(auths),
])
print(t)
@cli.command()
@click.argument('foreign_account', nargs=1, required=False)
@click.option('--permission', default="posting", help='The permission to grant (defaults to "posting")')
@click.option('--account', '-a', help='The account to allow action for')
@click.option('--weight', '-w', help='The weight to use instead of the (full) threshold. '
'If the weight is smaller than the threshold, '
'additional signatures are required')
@click.option('--threshold', '-t', help='The permission\'s threshold that needs to be reached '
'by signatures to be able to interact')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def allow(foreign_account, permission, account, weight, threshold, export):
"""Allow an account/key to interact with your account
foreign_account: The account or key that will be allowed to interact with account.
When not given, password will be asked, from which a public key is derived.
This derived key will then interact with your account.
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
if permission not in ["posting", "active", "owner"]:
print("Wrong permission, please use: posting, active or owner!")
return
acc = Account(account, blockchain_instance=stm)
if not foreign_account:
from beemgraphenebase.account import PasswordKey
pwd = click.prompt("Password for Key Derivation", confirmation_prompt=True, hide_input=True)
foreign_account = format(PasswordKey(account, pwd, permission).get_public(), stm.prefix)
if threshold is not None:
threshold = int(threshold)
tx = acc.allow(foreign_account, weight=weight, permission=permission, threshold=threshold)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('foreign_account', nargs=1, required=False)
@click.option('--permission', '-p', default="posting", help='The permission to grant (defaults to "posting")')
@click.option('--account', '-a', help='The account to disallow action for')
@click.option('--threshold', '-t', help='The permission\'s threshold that needs to be reached '
'by signatures to be able to interact')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def disallow(foreign_account, permission, account, threshold, export):
"""Remove allowance an account/key to interact with your account"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
if permission not in ["posting", "active", "owner"]:
print("Wrong permission, please use: posting, active or owner!")
return
if threshold is not None:
threshold = int(threshold)
acc = Account(account, blockchain_instance=stm)
if not foreign_account:
from beemgraphenebase.account import PasswordKey
pwd = click.prompt("Password for Key Derivation", confirmation_prompt=True)
foreign_account = [format(PasswordKey(account, pwd, permission).get_public(), stm.prefix)]
tx = acc.disallow(foreign_account, permission=permission, threshold=threshold)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('creator', nargs=1, required=True)
@click.option('--fee', help='When fee is 0 (default) a subsidized account is claimed and can be created later with create_claimed_account', default=0.0)
@click.option('--number', '-n', help='Number of subsidized accounts to be claimed (default = 1), when fee = 0 STEEM', default=1)
@click.option('--export', '-e', help='When set, transaction is stored in a file (should be used with number = 1)')
def claimaccount(creator, fee, number, export):
"""Claim account for claimed account creation."""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not creator:
creator = stm.config["default_account"]
if not unlock_wallet(stm):
return
creator = Account(creator, blockchain_instance=stm)
fee = Amount("%.3f %s" % (float(fee), stm.token_symbol), blockchain_instance=stm)
tx = None
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.claim_account(creator, fee=fee)
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.claim_account(creator, fee=fee)
tx = stm.hivesigner.url_from_tx(tx)
elif float(fee) == 0:
rc = RC(blockchain_instance=stm)
current_costs = rc.claim_account(tx_size=200)
current_mana = creator.get_rc_manabar()["current_mana"]
last_mana = current_mana
cnt = 0
print("Current costs %.2f G RC - current mana %.2f G RC" % (current_costs / 1e9, current_mana / 1e9))
print("Account can claim %d accounts" % (int(current_mana / current_costs)))
while current_costs + 10 < current_mana and cnt < number:
if cnt > 0:
print("Current costs %.2f G RC - current mana %.2f G RC" % (current_costs / 1e9, current_mana / 1e9))
tx = json.dumps(tx, indent=4)
print(tx)
cnt += 1
tx = stm.claim_account(creator, fee=fee)
time.sleep(10)
creator.refresh()
current_mana = creator.get_rc_manabar()["current_mana"]
print("Account claimed and %.2f G RC paid." % ((last_mana - current_mana) / 1e9))
last_mana = current_mana
if cnt == 0:
print("Not enough RC for a claim!")
else:
tx = stm.claim_account(creator, fee=fee)
if tx is not None:
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('account', nargs=1, required=True)
@click.option('--owner', help='Main owner public key - when not given, a passphrase is used to create keys.')
@click.option('--active', help='Active public key - when not given, a passphrase is used to create keys.')
@click.option('--posting', help='posting public key - when not given, a passphrase is used to create keys.')
@click.option('--memo', help='Memo public key - when not given, a passphrase is used to create keys.')
@click.option('--import-pub', '-i', help='Load public keys from file.')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def changekeys(account, owner, active, posting, memo, import_pub, export):
"""Changes all keys for the specified account
Keys are given in their public form.
Asks for the owner key for broadcasting the op to the chain."""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
account = Account(account, blockchain_instance=stm)
if import_pub and import_pub != "":
owner, active, posting, memo = import_pubkeys(import_pub)
if owner is None and active is None and memo is None and posting is None:
raise ValueError("All pubkeys are None or empty!")
if owner == "" or owner is None:
owner = account["owner"]["key_auths"][0][0]
if active == "" or active is None:
active = account["active"]["key_auths"][0][0]
if posting == "" or posting is None:
posting = account["posting"]["key_auths"][0][0]
if memo == "" or memo is None:
memo = account["memo_key"]
t = PrettyTable(["Key", "Value"])
t.align = "l"
t.add_row(["account", account["name"]])
t.add_row(["new owner pubkey", str(owner)])
t.add_row(["new active pubkey", str(active)])
t.add_row(["new posting pubkey", str(posting)])
t.add_row(["new memo pubkey", str(memo)])
print(t)
if not stm.unsigned:
wif = click.prompt('Owner key for %s' % account["name"], confirmation_prompt=False, hide_input=True)
stm.wallet.setKeys([wif])
tx = stm.update_account(account, owner_key=owner, active_key=active,
posting_key=posting, memo_key=memo, password=None)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('accountname', nargs=1, required=True)
@click.option('--account', '-a', help='Account that pays the fee or uses account tickets')
@click.option('--owner', help='Main public owner key - when not given, a passphrase is used to create keys.')
@click.option('--active', help='Active public key - when not given, a passphrase is used to create keys.')
@click.option('--memo', help='Memo public key - when not given, a passphrase is used to create keys.')
@click.option('--posting', help='posting public key - when not given, a passphrase is used to create keys.')
@click.option('--wif', '-w', help='Defines how many times the password is replaced by its WIF representation for password based keys (default = 0).', default=0)
@click.option('--create-claimed-account', '-c', help='Instead of paying the account creation fee a subsidized account is created.', is_flag=True, default=False)
@click.option('--import-pub', '-i', help='Load public keys from file.')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def newaccount(accountname, account, owner, active, memo, posting, wif, create_claimed_account, import_pub, export):
"""Create a new account
Default setting is that a fee is payed for account creation
Use --create-claimed-account for free account creation
Please use keygen and set public keys
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
if import_pub and import_pub != "":
owner, active, posting, memo = import_pubkeys(import_pub)
if create_claimed_account:
tx = stm.create_claimed_account(accountname, creator=acc, owner_key=owner, active_key=active, memo_key=memo, posting_key=posting)
else:
tx = stm.create_account(accountname, creator=acc, owner_key=owner, active_key=active, memo_key=memo, posting_key=posting)
elif owner is None or active is None or memo is None or posting is None:
import_password = click.prompt("Keys were not given - Passphrase is used to create keys\n New Account Passphrase", confirmation_prompt=True, hide_input=True)
if not import_password:
print("You cannot chose an empty password")
return
password = generate_password(import_password, wif)
if create_claimed_account:
tx = stm.create_claimed_account(accountname, creator=acc, password=password)
else:
tx = stm.create_account(accountname, creator=acc, password=password)
else:
if create_claimed_account:
tx = stm.create_claimed_account(accountname, creator=acc, owner_key=owner, active_key=active, memo_key=memo, posting_key=posting)
else:
tx = stm.create_account(accountname, creator=acc, owner_key=owner, active_key=active, memo_key=memo, posting_key=posting)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('variable', nargs=1, required=False)
@click.argument('value', nargs=1, required=False)
@click.option('--account', '-a', help='setprofile as this user')
@click.option('--pair', '-p', help='"Key=Value" pairs', multiple=True)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def setprofile(variable, value, account, pair, export):
"""Set a variable in an account\'s profile"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
keys = []
values = []
if pair:
for p in pair:
key, value = p.split("=")
keys.append(key)
values.append(value)
if variable and value:
keys.append(variable)
values.append(value)
profile = Profile(keys, values)
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
json_metadata = Profile(acc["json_metadata"] if acc["json_metadata"] else {})
json_metadata.update(profile)
tx = acc.update_account_profile(json_metadata)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('variable', nargs=-1, required=True)
@click.option('--account', '-a', help='delprofile as this user')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def delprofile(variable, account, export):
"""Delete a variable in an account\'s profile"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
json_metadata = Profile(acc["json_metadata"])
for var in variable:
json_metadata.remove(var)
tx = acc.update_account_profile(json_metadata)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('account', nargs=1, required=True)
@click.option('--roles', '-r', help='Import specified keys (owner, active, posting, memo).', default=["active", "posting", "memo"])
@click.option('--import-coldcard', '-i', help='Text file with a BIP85 WIF generated by a coldcard. The imported WIF is used as passphrase')
@click.option('--wif', '-w', help='Defines how many times the password is replaced by its WIF representation for password based keys (default = 0 or 1 when importing a cold card wif).')
def importaccount(account, roles, import_coldcard, wif):
"""Import an account using a passphrase"""
from beemgraphenebase.account import PasswordKey
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm):
return
account = Account(account, blockchain_instance=stm)
imported = False
if import_coldcard is None:
password = click.prompt("Account Passphrase", confirmation_prompt=False, hide_input=True)
if not password:
print("You cannot chose an empty Passphrase")
return
else:
password, path = import_coldcard_wif(import_coldcard)
if wif is not None:
wif = int(wif)
elif import_coldcard is not None:
wif = 1
else:
wif = 0
password = generate_password(password, wif)
if "owner" in roles:
owner_key = PasswordKey(account["name"], password, role="owner")
owner_pubkey = format(owner_key.get_public_key(), stm.prefix)
if owner_pubkey in [x[0] for x in account["owner"]["key_auths"]]:
print("Importing owner key!")
owner_privkey = owner_key.get_private_key()
stm.wallet.addPrivateKey(owner_privkey)
imported = True
if "active" in roles:
active_key = PasswordKey(account["name"], password, role="active")
active_pubkey = format(active_key.get_public_key(), stm.prefix)
if active_pubkey in [x[0] for x in account["active"]["key_auths"]]:
print("Importing active key!")
active_privkey = active_key.get_private_key()
stm.wallet.addPrivateKey(active_privkey)
imported = True
if "posting" in roles:
posting_key = PasswordKey(account["name"], password, role="posting")
posting_pubkey = format(posting_key.get_public_key(), stm.prefix)
if posting_pubkey in [
x[0] for x in account["posting"]["key_auths"]
]:
print("Importing posting key!")
posting_privkey = posting_key.get_private_key()
stm.wallet.addPrivateKey(posting_privkey)
imported = True
if "memo" in roles:
memo_key = PasswordKey(account["name"], password, role="memo")
memo_pubkey = format(memo_key.get_public_key(), stm.prefix)
if memo_pubkey == account["memo_key"]:
print("Importing memo key!")
memo_privkey = memo_key.get_private_key()
stm.wallet.addPrivateKey(memo_privkey)
imported = True
if not imported:
print("No matching key(s) found. Password correct?")
@cli.command()
@click.option('--account', '-a', help='The account to updatememokey action for')
@click.option('--key', help='The new memo key')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def updatememokey(account, key, export):
"""Update an account\'s memo key"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
if not key:
from beemgraphenebase.account import PasswordKey
pwd = click.prompt("Password for Memo Key Derivation", confirmation_prompt=True, hide_input=True)
memo_key = PasswordKey(account, pwd, "memo")
key = format(memo_key.get_public_key(), stm.prefix)
memo_privkey = memo_key.get_private_key()
if not stm.nobroadcast:
stm.wallet.addPrivateKey(memo_privkey)
tx = acc.update_memo_key(key)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('authorperm', nargs=1)
@click.argument('beneficiaries', nargs=-1)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def beneficiaries(authorperm, beneficiaries, export):
"""Set beneficaries"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
c = Comment(authorperm, blockchain_instance=stm)
account = c["author"]
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
options = {"author": c["author"],
"permlink": c["permlink"],
"max_accepted_payout": c["max_accepted_payout"],
"allow_votes": c["allow_votes"],
"allow_curation_rewards": c["allow_curation_rewards"]}
if "percent_steem_dollars" in c:
options["percent_steem_dollars"] = c["percent_steem_dollars"]
elif "percent_hbd" in c:
options["percent_hbd"] = c["percent_hbd"]
if isinstance(beneficiaries, tuple) and len(beneficiaries) == 1:
beneficiaries = beneficiaries[0].split(",")
beneficiaries_list_sorted = derive_beneficiaries(beneficiaries)
for b in beneficiaries_list_sorted:
Account(b["account"], blockchain_instance=stm)
tx = stm.comment_options(options, authorperm, beneficiaries_list_sorted, account=account)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('message_file', nargs=1, required=False)
@click.option('--account', '-a', help='Account which should sign')
@click.option('--verify', '-v', help='Verify a message instead of signing it', is_flag=True, default=False)
def message(message_file, account, verify):
"""Sign and verify a message
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if message_file is not None:
with open(message_file) as f:
message = f.read()
elif verify:
print("Please store the signed message into a text file and append the file path to beempy message -v")
return
else:
message = input("Enter message: ")
m = Message(message, blockchain_instance=stm)
if verify:
if m.verify():
print("Could verify message!")
else:
print("Could not verify message!")
else:
if not unlock_wallet(stm):
return
out = m.sign(account)
if message_file is not None:
with open(message_file, "w", encoding="utf-8") as f:
f.write(out)
else:
print(out)
@cli.command()
@click.argument('memo', nargs=-1)
@click.option('--account', '-a', help='Account which decrypts the memo with its memo key')
@click.option('--output', '-o', help='Output file name. Result is stored, when set instead of printed.')
@click.option('--info', '-i', help='Shows information about public keys and used nonce', is_flag=True, default=False)
@click.option('--text', '-t', help='Reads the text file content', is_flag=True, default=False)
@click.option('--binary', '-b', help='Reads the binary file content', is_flag=True, default=False)
def decrypt(memo, account, output, info, text, binary):
"""decrypt a (or more than one) decrypted memo/file with your memo key
"""
if text and binary:
print("You cannot set text and binary!")
return
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
m = Memo(from_account=None, to_account=account, blockchain_instance=stm)
if not unlock_wallet(stm):
return
for entry in memo:
print("\n")
if not binary and info:
from_key, to_key, nonce = m.extract_decrypt_memo_data(entry)
try:
from_account = stm.wallet.getAccountFromPublicKey(str(from_key))
to_account = stm.wallet.getAccountFromPublicKey(str(to_key))
if from_account is not None:
print("from: %s" % str(from_account))
else:
print("from: %s" % str(from_key))
if to_account is not None:
print("to: %s" % str(to_account))
else:
print("to: %s" % str(to_key))
print("nonce: %s" % nonce)
except:
print("from: %s" % str(from_key))
print("to: %s" % str(to_key))
print("nonce: %s" % nonce)
if text:
with open(entry) as f:
message = f.read()
elif binary:
if output is None:
output = entry + ".dec"
ret = m.decrypt_binary(entry, output, buffer_size=2048)
if info:
t = PrettyTable(["Key", "Value"])
t.align = "l"
t.add_row(["file", entry])
for key in ret:
t.add_row([key, ret[key]])
print(t)
else:
message = entry
if text:
out = m.decrypt(message)
if output is None:
output = entry
with open(output, "w", encoding="utf-8") as f:
f.write(out)
elif not binary:
out = m.decrypt(message)
if info:
print("message: %s" % out)
if output:
with open(output, "w", encoding="utf-8") as f:
f.write(out)
elif not info:
print(out)
@cli.command()
@click.argument('receiver', nargs=1)
@click.argument('memo', nargs=-1)
@click.option('--account', '-a', help='Account which encrypts the memo with its memo key')
@click.option('--output', '-o', help='Output file name. Result is stored, when set instead of printed.')
@click.option('--text', '-t', help='Reads the text file content', is_flag=True, default=False)
@click.option('--binary', '-b', help='Reads the binary file content', is_flag=True, default=False)
def encrypt(receiver, memo, account, output, text, binary):
"""encrypt a (or more than one) memo text/file with the your memo key
"""
if text and binary:
print("You cannot set text and binary!")
return
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
m = Memo(from_account=account, to_account=receiver, blockchain_instance=stm)
if not unlock_wallet(stm):
return
for entry in memo:
print("\n")
if text:
with open(entry) as f:
message = f.read()
if message[0] == "#":
message = message[1:]
elif binary:
if output is None:
output = entry + ".enc"
m.encrypt_binary(entry, output, buffer_size=2048)
else:
message = entry
if message[0] == "#":
message = message[1:]
if text:
out = m.encrypt(message)["message"]
if output is None:
output = entry
with open(output, "w", encoding="utf-8") as f:
f.write(out)
elif not binary:
out = m.encrypt(message)["message"]
if output is None:
print(out)
else:
with open(output, "w", encoding="utf-8") as f:
f.write(out)
@cli.command()
@click.argument('image', nargs=1)
@click.option('--account', '-a', help='Account name')
@click.option('--image-name', '-n', help='Image name')
def uploadimage(image, account, image_name):
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
iu = ImageUploader(blockchain_instance=stm)
tx = iu.upload(image, account, image_name)
if image_name is None:
print("" % tx["url"])
else:
print("" % (image_name, tx["url"]))
@cli.command()
@click.argument('permlink', nargs=-1)
@click.option('--account', '-a', help='Account are you posting from')
@click.option('--save', '-s', help="Saves markdown in current directoy as date_permlink.md", is_flag=True, default=False)
@click.option('--export', '-e', default=None, help="Export markdown to given a md-file name")
def download(permlink, account, save, export):
"""Download body with yaml header"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if account is None:
account = stm.config["default_account"]
account = Account(account, blockchain_instance=stm)
if len(permlink) == 0:
permlink = []
progress_length = account.virtual_op_count()
print("Reading post history...")
last_index = 0
with click.progressbar(length=progress_length) as bar:
for h in account.history(only_ops=["comment"]):
if h["parent_author"] != '':
continue
if h["author"] != account["name"]:
continue
if h["permlink"] in permlink:
continue
else:
permlink.append(h["permlink"])
bar.update(h["index"] - last_index)
last_index = h["index"]
for p in permlink:
if p[0] == "@":
authorperm = p
elif os.path.exists(p):
with open(p) as f:
content = f.read()
body, parameter = seperate_yaml_dict_from_body(content)
if "author" in parameter and "permlink" in parameter:
authorperm = construct_authorperm(parameter["author"], parameter["permlink"])
else:
authorperm = construct_authorperm(account["name"], p)
else:
authorperm = construct_authorperm(account["name"], p)
if len(permlink) > 1:
print(authorperm)
comment = Comment(authorperm, blockchain_instance=stm)
if comment.parent_author != "" and comment.parent_permlink != "":
reply_identifier = construct_authorperm(comment.parent_author, comment.parent_permlink)
else:
reply_identifier = None
yaml_prefix = '---\n'
if comment["title"] != "":
yaml_prefix += 'title: "%s"\n' % comment["title"]
yaml_prefix += 'permlink: %s\n' % comment["permlink"]
yaml_prefix += 'author: %s\n' % comment["author"]
if "author" in comment.json_metadata:
yaml_prefix += 'authored by: %s\n' % comment.json_metadata["author"]
if "description" in comment.json_metadata:
yaml_prefix += 'description: "%s"\n' % comment.json_metadata["description"]
if "canonical_url" in comment.json_metadata:
yaml_prefix += 'canonical_url: %s\n' % comment.json_metadata["canonical_url"]
if "app" in comment.json_metadata:
yaml_prefix += 'app: %s\n' % comment.json_metadata["app"]
if "last_update" in comment.json():
yaml_prefix += 'last_update: %s\n' % comment.json()["last_update"]
else:
yaml_prefix += 'last_update: %s\n' % comment.json()["updated"]
yaml_prefix += 'max_accepted_payout: %s\n' % str(comment["max_accepted_payout"])
if "percent_steem_dollars" in comment:
yaml_prefix += 'percent_steem_dollars: %s\n' % str(comment["percent_steem_dollars"])
elif "percent_hbd" in comment:
yaml_prefix += 'percent_hbd: %s\n' % str(comment["percent_hbd"])
if "tags" in comment.json_metadata:
if len(comment.json_metadata["tags"]) > 0 and comment["category"] != comment.json_metadata["tags"][0] and len(comment["category"]) > 0:
yaml_prefix += 'community: %s\n' % comment["category"]
yaml_prefix += 'tags: %s\n' % ",".join(comment.json_metadata["tags"])
if "beneficiaries" in comment:
beneficiaries = []
for b in comment["beneficiaries"]:
beneficiaries.append("%s:%.2f%%" % (b["account"], b["weight"] / 10000 * 100))
if len(beneficiaries) > 0:
yaml_prefix += 'beneficiaries: %s\n' % ",".join(beneficiaries)
if reply_identifier is not None:
yaml_prefix += 'reply_identifier: %s\n' % reply_identifier
yaml_prefix += '---\n'
if save or export is not None:
if export is None or len(permlink) > 0:
export = comment.json()["created"].replace(":", "-") + "_" + comment["permlink"] + ".md"
if export[-3:] != ".md":
export += ".md"
with open(export, "w", encoding="utf-8") as f:
f.write(yaml_prefix + comment["body"])
else:
print(yaml_prefix + comment["body"])
@cli.command()
@click.argument('markdown-file', nargs=1)
@click.option('--account', '-a', help='Account are you posting from')
@click.option('--title', '-t', help='Title of the post')
@click.option('--tags', '-g', help='A komma separated list of tags to go with the post.')
@click.option('--community', '-c', help=' Name of the community (optional)')
@click.option('--beneficiaries', '-b', help='Post beneficiaries (komma separated, e.g. a:10%,b:20%)')
@click.option('--percent-steem-dollars', '-d', help='50% SBD /50% SP is 10000 (default), 100% SP is 0')
@click.option('--percent-hbd', '-h', help='50% HBD /50% HP is 10000 (default), 100% HP is 0')
@click.option('--max-accepted-payout', '-m', help='Default is 1000000.000 [SBD]')
@click.option('--no-parse-body', '-n', help='Disable parsing of links, tags and images', is_flag=True, default=False)
def createpost(markdown_file, account, title, tags, community, beneficiaries, percent_steem_dollars, percent_hbd, max_accepted_payout, no_parse_body):
"""Creates a new markdown file with YAML header"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if account is None:
account = input("author: ")
if title is None:
title = input("title: ")
if tags is None:
tags = input("tags (comma seperated): ")
if community is None:
community_found = False
while not community_found:
community = input("community account (name or title): ")
try:
community = Community(community)
except:
c = Communities(limit=1000)
comm_cand = c.search_title(community)
if len(comm_cand) == 0:
print("No community could be found!")
continue
print(comm_cand.printAsTable())
index = input("Enter community Nr:")
if int(index) - 1 >= len(comm_cand):
continue
community = comm_cand[int(index) - 1]
ret = input("Selected community: %s - %s [yes/no]? " % (community["name"], community["title"]))
if ret in ["y", "yes"]:
community_found = True
community = community["name"]
if beneficiaries is None:
beneficiaries = input("beneficiaries (komma separated, e.g. a:10%,b:20%) [return to skip]: ")
if percent_steem_dollars is None and percent_hbd is None:
ret = None
while ret is None:
ret = input("50% or 100% Steem/Hive Power as post reward [50 or 100]? ")
if ret not in ["50", "100"]:
ret = None
if ret == "50":
percent_steem_dollars = 10000
percent_hbd = 10000
else:
percent_steem_dollars = 0
percent_hbd = 0
elif percent_steem_dollars is not None and percent_hbd is not None:
raise ValueError("percent_hbd and percent_steem_dollars cannot be both set.")
elif percent_steem_dollars is None:
percent_steem_dollars = percent_hbd
elif percent_hbd is None:
percent_hbd = percent_steem_dollars
if max_accepted_payout is None:
max_accepted_payout = input("max accepted payout [return to skip]: ")
yaml_prefix = '---\n'
yaml_prefix += 'title: "%s"\n' % title
yaml_prefix += 'author: %s\n' % account
yaml_prefix += 'tags: %s\n' % tags
if stm.is_hive:
yaml_prefix += 'percent_hbd: %d\n' % percent_hbd
else:
yaml_prefix += 'percent_steem_dollars: %d\n' % percent_steem_dollars
if community is not None and community != "":
yaml_prefix += 'community: %s\n' % community
if beneficiaries is not None and beneficiaries != "":
yaml_prefix += 'beneficiaries: %s\n' % beneficiaries
if max_accepted_payout is not None and max_accepted_payout != "":
yaml_prefix += 'max_accepted_payout: %s\n' % max_accepted_payout
yaml_prefix += '---\n'
with open(markdown_file, "w", encoding="utf-8") as f:
f.write(yaml_prefix)
@cli.command()
@click.argument('markdown-file', nargs=1)
@click.option('--account', '-a', help='Account are you posting from')
@click.option('--title', '-t', help='Title of the post')
@click.option('--permlink', '-p', help='Manually set the permlink (optional)')
@click.option('--tags', '-g', help='A komma separated list of tags to go with the post.')
@click.option('--reply-identifier', '-r', help=' Identifier of the parent post/comment, when set a comment is broadcasted')
@click.option('--community', '-c', help=' Name of the community (optional)')
@click.option('--canonical-url', '-u', help='Canonical url, can also set to https://hive.blog or https://peakd.com (optional)')
@click.option('--beneficiaries', '-b', help='Post beneficiaries (komma separated, e.g. a:10%,b:20%)')
@click.option('--percent-steem-dollars', '-d', help='50% SBD /50% SP is 10000 (default), 100% SP is 0')
@click.option('--percent-hbd', '-h', help='50% SBD /50% SP is 10000 (default), 100% SP is 0')
@click.option('--max-accepted-payout', '-m', help='Default is 1000000.000 [SBD]')
@click.option('--no-parse-body', '-n', help='Disable parsing of links, tags and images', is_flag=True, default=False)
@click.option('--no-patch-on-edit', '-e', help='Disable patch posting on edits (when the permlink already exists)', is_flag=True, default=False)
@click.option('--export', help='When set, transaction is stored in a file')
def post(markdown_file, account, title, permlink, tags, reply_identifier, community, canonical_url, beneficiaries, percent_steem_dollars, percent_hbd, max_accepted_payout, no_parse_body, no_patch_on_edit, export):
"""broadcasts a post/comment. All image links which links to a file will be uploaded.
The yaml header can contain:
---
title: your title
tags: tag1,tag2
community: hive-100000
beneficiaries: beempy:5%,holger80:5%
---
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
with open(markdown_file) as f:
content = f.read()
body, parameter = seperate_yaml_dict_from_body(content)
if title is not None:
parameter["title"] = title
if account is not None:
parameter["author"] = account
if tags is not None:
parameter["tags"] = tags
if permlink is not None:
parameter["permlink"] = permlink
if beneficiaries is not None:
parameter["beneficiaries"] = beneficiaries
if community is not None:
parameter["community"] = community
if reply_identifier is not None:
parameter["reply_identifier"] = reply_identifier
if percent_steem_dollars is not None:
parameter["percent_steem_dollars"] = percent_steem_dollars
elif "percent-steem-dollars" in parameter:
parameter["percent_steem_dollars"] = parameter["percent-steem-dollars"]
if percent_hbd is not None:
parameter["percent_hbd"] = percent_hbd
elif "percent-hbd" in parameter:
parameter["percent_hbd"] = parameter["percent-hbd"]
if max_accepted_payout is not None:
parameter["max_accepted_payout"] = max_accepted_payout
elif "max-accepted-payout" in parameter:
parameter["max_accepted_payout"] = parameter["max-accepted-payout"]
if canonical_url is not None:
parameter["canonical_url"] = canonical_url
if not unlock_wallet(stm):
return
tags = None
if "tags" in parameter:
tags = derive_tags(parameter["tags"])
title = ""
if "title" in parameter:
title = parameter["title"]
if "author" in parameter:
author = parameter["author"]
else:
author = stm.config["default_account"]
permlink = None
if "permlink" in parameter:
permlink = parameter["permlink"]
reply_identifier = None
if "reply_identifier" in parameter:
reply_identifier = parameter["reply_identifier"]
community = None
if "community" in parameter:
community = parameter["community"]
if "parse_body" in parameter:
parse_body = bool(parameter["parse_body"])
else:
parse_body = not no_parse_body
max_accepted_payout = None
percent_steem_dollars = None
if "percent_steem_dollars" in parameter:
percent_steem_dollars = parameter["percent_steem_dollars"]
percent_hbd = None
if "percent_hbd" in parameter:
percent_hbd = parameter["percent_hbd"]
max_accepted_payout = None
if "max_accepted_payout" in parameter:
max_accepted_payout = parameter["max_accepted_payout"]
comment_options = None
if max_accepted_payout is not None or percent_steem_dollars is not None or percent_hbd is not None:
comment_options = {}
if max_accepted_payout is not None:
if stm.backed_token_symbol not in max_accepted_payout:
max_accepted_payout = str(Amount(float(max_accepted_payout), stm.backed_token_symbol, blockchain_instance=stm))
comment_options["max_accepted_payout"] = max_accepted_payout
if percent_hbd is not None and stm.is_hive:
comment_options["percent_hbd"] = percent_hbd
elif percent_steem_dollars is not None and stm.is_hive:
comment_options["percent_hbd"] = percent_steem_dollars
elif percent_steem_dollars is not None:
comment_options["percent_steem_dollars"] = percent_steem_dollars
elif percent_hbd is not None:
comment_options["percent_steem_dollars"] = percent_hbd
beneficiaries = None
if "beneficiaries" in parameter:
beneficiaries = derive_beneficiaries(parameter["beneficiaries"])
for b in beneficiaries:
Account(b["account"], blockchain_instance=stm)
if permlink is not None:
try:
comment = Comment(construct_authorperm(author, permlink), blockchain_instance=stm)
except:
comment = None
else:
comment = None
iu = ImageUploader(blockchain_instance=stm)
for link in list(re.findall(r'!\[[^"\'@\]\(]*\]\([^"\'@\(\)]*\.(?:png|jpg|jpeg|gif|png|svg)\)', body)):
image = link.split("(")[1].split(")")[0]
image_name = link.split("![")[1].split("]")[0]
if image[:4] == "http":
continue
if stm.unsigned:
continue
basepath = os.path.dirname(markdown_file)
if os.path.exists(image):
tx = iu.upload(image, author, image_name)
body = body.replace(image, tx["url"])
elif os.path.exists(os.path.join(basepath, image)):
tx = iu.upload(image, author, image_name)
body = body.replace(image, tx["url"])
if comment is None and permlink is None and reply_identifier is None:
permlink = derive_permlink(title, with_suffix=False)
try:
comment = Comment(construct_authorperm(author, permlink), blockchain_instance=stm)
except:
comment = None
if comment is None:
json_metadata = {}
else:
json_metadata = comment.json_metadata
if "authored_by" in parameter:
json_metadata["authored_by"] = parameter["authored_by"]
if "description" in parameter:
json_metadata["description"] = parameter["description"]
if "canonical_url" in parameter:
json_metadata["canonical_url"] = parameter["canonical_url"]
else:
json_metadata["canonical_url"] = stm.config["default_canonical_url"] or "https://hive.blog"
if "canonical_url" in json_metadata and json_metadata["canonical_url"].find("@") < 0:
if json_metadata["canonical_url"][-1] != "/":
json_metadata["canonical_url"] += "/"
if json_metadata["canonical_url"][:8] != 'https://':
json_metadata["canonical_url"] = 'https://' + json_metadata["canonical_url"]
if community is None:
json_metadata["canonical_url"] += tags[0] + "/@" + author + "/" + permlink
else:
json_metadata["canonical_url"] += community + "/@" + author + "/" + permlink
if comment is None or no_patch_on_edit:
if reply_identifier is None and (len(tags) == 0 or tags is None):
raise ValueError("Tags must not be empty!")
tx = stm.post(title, body, author=author, permlink=permlink, reply_identifier=reply_identifier, community=community,
tags=tags, json_metadata=json_metadata, comment_options=comment_options, beneficiaries=beneficiaries, parse_body=parse_body,
app='beempy/%s' % (__version__))
else:
patch_text = make_patch(comment.body, body)
if patch_text == "":
print("No changes on post body detected.")
else:
print(patch_text)
edit_ok = click.prompt("Should I broadcast %s [y/n]" % (str(permlink)))
if edit_ok not in ["y", "ye", "yes"]:
return
tx = stm.post(title, patch_text, author=author, permlink=permlink, reply_identifier=reply_identifier, community=community,
tags=tags, json_metadata=json_metadata, parse_body=False, app='beempy/%s' % (__version__))
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('authorperm', nargs=1)
@click.argument('body', nargs=1)
@click.option('--account', '-a', help='Account are you posting from')
@click.option('--title', '-t', help='Title of the post')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def reply(authorperm, body, account, title, export):
"""replies to a comment"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
if title is None:
title = ""
tx = stm.post(title, body, json_metadata=None, author=account, reply_identifier=authorperm,
app='beempy/%s' % (__version__))
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
@click.option('--account', '-a', help='Your account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def approvewitness(witness, account, export):
"""Approve a witnesses"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.approvewitness(witness, approve=True)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
@click.option('--account', '-a', help='Your account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def disapprovewitness(witness, account, export):
"""Disapprove a witnesses"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.disapprovewitness(witness)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('proxy', nargs=1)
@click.option('--account', '-a', help='Your account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def setproxy(proxy, account, export):
"""Set your witness/proposal system proxy"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.setproxy(proxy, account)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.option('--account', '-a', help='Your account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def delproxy(account, export):
"""Delete your witness/proposal system proxy"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.setproxy('', account)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.option('--file', '-i', help='Load transaction from file. If "-", read from stdin (defaults to "-")')
@click.option('--outfile', '-o', help='Load transaction from file. If "-", read from stdin (defaults to "-")')
def sign(file, outfile):
"""Sign a provided transaction with available and required keys"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm):
return
if file and file != "-":
if not os.path.isfile(file):
raise Exception("File %s does not exist!" % file)
with open(file) as fp:
tx = fp.read()
if tx.find('\0') > 0:
with open(file, encoding='utf-16') as fp:
tx = fp.read()
else:
tx = click.get_text_stream('stdin')
tx = ast.literal_eval(tx)
tx = stm.sign(tx, reconstruct_tx=False)
tx = json.dumps(tx, indent=4)
if outfile and outfile != "-":
with open(outfile, 'w') as fp:
fp.write(tx)
else:
print(tx)
@cli.command()
@click.option('--file', '-f', help='Load transaction from file. If "-", read from stdin (defaults to "-")')
def broadcast(file):
"""broadcast a signed transaction"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if file and file != "-":
if not os.path.isfile(file):
raise Exception("File %s does not exist!" % file)
with open(file) as fp:
tx = fp.read()
if tx.find('\0') > 0:
with open(file, encoding='utf-16') as fp:
tx = fp.read()
else:
tx = click.get_text_stream('stdin')
tx = ast.literal_eval(tx)
tx = stm.broadcast(tx)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.option('--lines', '-n', help='Defines how many ops should be shown', default=10)
@click.option('--head', '-h', help='Stream mode: When set, it is set to head (default is irreversible)', is_flag=True, default=False)
@click.option('--table', '-t', help='Output as table', is_flag=True, default=False)
@click.option('--follow', '-f', help='Constantly stream output', is_flag=True, default=False)
def stream(lines, head, table, follow):
""" Stream operations
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
mode = "irreversible"
if head:
mode = "head"
b = Blockchain(mode=mode, blockchain_instance=stm)
op_count = 0
if table:
import pprint
t = PrettyTable(["blocknum", "trx_num", "type", "content"])
t.align = "l"
t._max_width = {"content" : 80}
last_block_num = 0
for ops in b.stream(raw_ops=False):
op_count += 1
ops.pop("_id")
block_num = ops.pop("block_num")
ops_type = ops.pop("type")
if last_block_num == 0:
last_block_num = block_num
trx_num = ops.pop("trx_num")
for key in ops:
if isinstance(ops[key], dict) and "nai" in ops[key]:
ops[key] = str(Amount(ops[key], blockchain_instance=stm))
elif key == "timestamp":
ops[key] = formatTimeString(ops[key])
# value = json.dumps(ops, indent=4)
if last_block_num < block_num:
print(t)
t = PrettyTable(["blocknum", "trx_num", "type", "content"])
t.align = "l"
t._max_width = {"content" : 80}
last_block_num = block_num
content = ops
if ops_type == "custom_json":
content = ops["id"]
elif ops_type == "vote":
content = "%.2f%% @%s/%s - %s" % (ops["weight"] / 100, ops["author"], ops["permlink"][:30], ops["voter"])
elif ops_type == "transfer":
content = "%s: @%s -> @%s" % (str(ops["amount"]), ops["from"], ops["to"])
elif ops_type == "transfer_to_vesting":
content = "%s: @%s -> @%s" % (str(ops["amount"]), ops["from"], ops["to"])
t.add_row([str(block_num), str(trx_num), ops_type, content])
if op_count >= lines and not follow:
print(t)
return
else:
import pprint
for ops in b.stream(raw_ops=True):
op_count += 1
ops["timestamp"] = formatTimeString(ops["timestamp"])
pprint.pprint(ops)
if op_count >= lines and not follow:
return
@cli.command()
@click.option('--sbd-to-steem', help='Show ticker in SBD/STEEM', is_flag=True, default=False)
@click.option('--hbd-to-hive', '-i', help='Show ticker in HBD/HIVE', is_flag=True, default=False)
def ticker(sbd_to_steem, hbd_to_hive):
""" Show ticker
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
t = PrettyTable(["Key", "Value"])
t.align = "l"
market = Market(blockchain_instance=stm)
ticker = market.ticker()
for key in ticker:
if key in ["highest_bid", "latest", "lowest_ask"] and (sbd_to_steem or hbd_to_hive):
t.add_row([key, str(ticker[key].as_base(stm.backed_token_symbol))])
elif key in "percent_change" and (sbd_to_steem or hbd_to_hive):
t.add_row([key, "%.2f %%" % -ticker[key]])
elif key in "percent_change":
t.add_row([key, "%.2f %%" % ticker[key]])
else:
t.add_row([key, str(ticker[key])])
print(t)
@cli.command()
@click.option('--width', '-w', help='Plot width (default 75)', default=75)
@click.option('--height', '-h', help='Plot height (default 15)', default=15)
@click.option('--ascii', help='Use only ascii symbols', is_flag=True, default=False)
def pricehistory(width, height, ascii):
""" Show price history
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
feed_history = stm.get_feed_history()
current_base = Amount(feed_history['current_median_history']["base"], blockchain_instance=stm)
current_quote = Amount(feed_history['current_median_history']["quote"], blockchain_instance=stm)
price_history = feed_history["price_history"]
price = []
for h in price_history:
base = Amount(h["base"], blockchain_instance=stm)
quote = Amount(h["quote"], blockchain_instance=stm)
price.append(float(base.amount / quote.amount))
if ascii:
charset = u'ascii'
else:
charset = u'utf8'
chart = AsciiChart(height=height, width=width, offset=4, placeholder='{:6.2f} $', charset=charset)
print("\n Price history for %s (median price %4.2f $)\n" % (stm.token_symbol, float(current_base) / float(current_quote)))
chart.adapt_on_series(price)
chart.new_chart()
chart.add_axis()
if (float(current_base) / float(current_quote)) <= max(price):
chart._draw_h_line(chart._map_y(float(current_base) / float(current_quote)), 1, int(chart.n / chart.skip), line=chart.char_set["curve_hl_dot"])
chart.add_curve(price)
print(str(chart))
@cli.command()
@click.option('--days', '-d', help='Limit the days of shown trade history (default 7)', default=7.)
@click.option('--hours', help='Limit the intervall history intervall (default 2 hours)', default=2.0)
@click.option('--sbd-to-steem', help='Show ticker in SBD/STEEM', is_flag=True, default=False)
@click.option('--hbd-to-hive', '-i', help='Show ticker in HBD/HIVE', is_flag=True, default=False)
@click.option('--limit', '-l', help='Limit number of trades which is fetched at each intervall point (default 100)', default=100)
@click.option('--width', '-w', help='Plot width (default 75)', default=75)
@click.option('--height', '-h', help='Plot height (default 15)', default=15)
@click.option('--ascii', help='Use only ascii symbols', is_flag=True, default=False)
def tradehistory(days, hours, sbd_to_steem, hbd_to_hive, limit, width, height, ascii):
""" Show price history
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
m = Market(blockchain_instance=stm)
utc = pytz.timezone('UTC')
stop = utc.localize(datetime.utcnow())
start = stop - timedelta(days=days)
intervall = timedelta(hours=hours)
trades = m.trade_history(start=start, stop=stop, limit=limit, intervall=intervall)
price = []
if sbd_to_steem or hbd_to_hive:
base_str = stm.token_symbol
else:
base_str = stm.backed_token_symbol
for trade in trades:
base = 0
quote = 0
for order in trade:
base += float(order.as_base(base_str)["base"])
quote += float(order.as_base(base_str)["quote"])
price.append(base / quote)
if ascii:
charset = u'ascii'
else:
charset = u'utf8'
chart = AsciiChart(height=height, width=width, offset=3, placeholder='{:6.2f} ', charset=charset)
if sbd_to_steem or hbd_to_hive:
print("\n Trade history %s - %s \n\n%s/%s" % (formatTimeString(start), formatTimeString(stop),
stm.backed_token_symbol, stm.token_symbol))
else:
print("\n Trade history %s - %s \n\n%s/%s" % (formatTimeString(start), formatTimeString(stop),
stm.token_symbol, stm.backed_token_symbol))
chart.adapt_on_series(price)
chart.new_chart()
chart.add_axis()
chart.add_curve(price)
print(str(chart))
@cli.command()
@click.option('--chart', help='Enable charting', is_flag=True)
@click.option('--limit', '-l', help='Limit number of returned open orders (default 25)', default=25)
@click.option('--show-date', help='Show dates', is_flag=True, default=False)
@click.option('--width', '-w', help='Plot width (default 75)', default=75)
@click.option('--height', '-h', help='Plot height (default 15)', default=15)
@click.option('--ascii', help='Use only ascii symbols', is_flag=True, default=False)
def orderbook(chart, limit, show_date, width, height, ascii):
"""Obtain orderbook of the internal market"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
market = Market(blockchain_instance=stm)
orderbook = market.orderbook(limit=limit, raw_data=False)
if not show_date:
header = ["Asks Sum " + stm.backed_token_symbol, "Sell Orders", "Bids Sum " + stm.backed_token_symbol, "Buy Orders"]
else:
header = ["Asks date", "Sell Orders", "Bids date", "Buy Orders"]
t = PrettyTable(header, hrules=0)
t.align = "r"
asks = []
bids = []
asks_date = []
bids_date = []
sumsum_asks = []
sum_asks = 0
sumsum_bids = []
sum_bids = 0
n = 0
for order in orderbook["asks"]:
asks.append(order)
sum_asks += float(order.as_base(stm.backed_token_symbol)["base"])
sumsum_asks.append(sum_asks)
if n < len(asks):
n = len(asks)
for order in orderbook["bids"]:
bids.append(order)
sum_bids += float(order.as_base(stm.backed_token_symbol)["base"])
sumsum_bids.append(sum_bids)
if n < len(bids):
n = len(bids)
if show_date:
for order in orderbook["asks_date"]:
asks_date.append(order)
if n < len(asks_date):
n = len(asks_date)
for order in orderbook["bids_date"]:
bids_date.append(order)
if n < len(bids_date):
n = len(bids_date)
if chart:
if ascii:
charset = u'ascii'
else:
charset = u'utf8'
chart = AsciiChart(height=height, width=width, offset=4, placeholder=' {:10.2f} $', charset=charset)
print("\n Orderbook \n")
chart.adapt_on_series(sumsum_asks[::-1] + sumsum_bids)
chart.new_chart()
chart.add_axis()
y0 = chart._map_y(chart.minimum)
y1 = chart._map_y(chart.maximum)
chart._draw_v_line(y0 + 1, y1, int(chart.n / chart.skip / 2), line=chart.char_set["curve_vl_dot"])
chart.add_curve(sumsum_asks[::-1] + sumsum_bids)
print(str(chart))
return
for i in range(n):
row = []
if len(asks_date) > i:
row.append(formatTimeString(asks_date[i]))
elif show_date:
row.append([""])
if len(sumsum_asks) > i and not show_date:
row.append("%.2f" % sumsum_asks[i])
elif not show_date:
row.append([""])
if len(asks) > i:
row.append(str(asks[i]))
else:
row.append([""])
if len(bids_date) > i:
row.append(formatTimeString(bids_date[i]))
elif show_date:
row.append([""])
if len(sumsum_bids) > i and not show_date:
row.append("%.2f" % sumsum_bids[i])
elif not show_date:
row.append([""])
if len(bids) > i:
row.append(str(bids[i]))
else:
row.append([""])
t.add_row(row)
print(t)
@cli.command()
@click.argument('amount', nargs=1)
@click.argument('asset', nargs=1)
@click.argument('price', nargs=1, required=False)
@click.option('--account', '-a', help='Buy with this account (defaults to "default_account")')
@click.option('--orderid', help='Set an orderid')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def buy(amount, asset, price, account, orderid, export):
"""Buy STEEM/HIVE or SBD/HBD from the internal market
Limit buy price denoted in (SBD per STEEM or HBD per HIVE)
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if account is None:
account = stm.config["default_account"]
if asset == stm.backed_token_symbol:
market = Market(base=Asset(stm.token_symbol), quote=Asset(stm.backed_token_symbol), blockchain_instance=stm)
else:
market = Market(base=Asset(stm.backed_token_symbol), quote=Asset(stm.token_symbol), blockchain_instance=stm)
if price is None:
orderbook = market.orderbook(limit=1, raw_data=False)
if asset == stm.token_symbol and len(orderbook["bids"]) > 0:
p = Price(orderbook["bids"][0]["base"], orderbook["bids"][0]["quote"], blockchain_instance=stm).invert()
p_show = p
elif len(orderbook["asks"]) > 0:
p = Price(orderbook["asks"][0]["base"], orderbook["asks"][0]["quote"], blockchain_instance=stm).invert()
p_show = p
price_ok = click.prompt("Is the following Price ok: %s [y/n]" % (str(p_show)))
if price_ok not in ["y", "ye", "yes"]:
return
else:
p = Price(float(price), u"%s:%s" % (stm.backed_token_symbol, stm.token_symbol), blockchain_instance=stm)
if not unlock_wallet(stm):
return
a = Amount(float(amount), asset, blockchain_instance=stm)
acc = Account(account, blockchain_instance=stm)
tx = market.buy(p, a, account=acc, orderid=orderid)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('amount', nargs=1)
@click.argument('asset', nargs=1)
@click.argument('price', nargs=1, required=False)
@click.option('--account', '-a', help='Sell with this account (defaults to "default_account")')
@click.option('--orderid', help='Set an orderid')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def sell(amount, asset, price, account, orderid, export):
"""Sell STEEM/HIVE or SBD/HBD from the internal market
Limit sell price denoted in (SBD per STEEM) or (HBD per HIVE)
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if asset == stm.backed_token_symbol:
market = Market(base=Asset(stm.token_symbol), quote=Asset(stm.backed_token_symbol), blockchain_instance=stm)
else:
market = Market(base=Asset(stm.backed_token_symbol), quote=Asset(stm.token_symbol), blockchain_instance=stm)
if not account:
account = stm.config["default_account"]
if not price:
orderbook = market.orderbook(limit=1, raw_data=False)
if asset == stm.backed_token_symbol and len(orderbook["bids"]) > 0:
p = Price(orderbook["bids"][0]["base"], orderbook["bids"][0]["quote"], blockchain_instance=stm).invert()
p_show = p
else:
p = Price(orderbook["asks"][0]["base"], orderbook["asks"][0]["quote"], blockchain_instance=stm).invert()
p_show = p
price_ok = click.prompt("Is the following Price ok: %s [y/n]" % (str(p_show)))
if price_ok not in ["y", "ye", "yes"]:
return
else:
p = Price(float(price), u"%s:%s" % (stm.backed_token_symbol, stm.token_symbol), blockchain_instance=stm)
if not unlock_wallet(stm):
return
a = Amount(float(amount), asset, blockchain_instance=stm)
acc = Account(account, blockchain_instance=stm)
tx = market.sell(p, a, account=acc, orderid=orderid)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('orderid', nargs=1)
@click.option('--account', '-a', help='Sell with this account (defaults to "default_account")')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def cancel(orderid, account, export):
"""Cancel order in the internal market"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
market = Market(blockchain_instance=stm)
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = market.cancel(orderid, account=acc)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('account', nargs=1, required=False)
def openorders(account):
"""Show open orders"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
market = Market(blockchain_instance=stm)
if not account:
account = stm.config["default_account"]
acc = Account(account, blockchain_instance=stm)
openorders = market.accountopenorders(account=acc)
t = PrettyTable(["Orderid", "Created", "Order", "Account"], hrules=0)
t.align = "r"
for order in openorders:
t.add_row([order["orderid"],
formatTimeString(order["created"]),
str(order["order"]),
account])
print(t)
@cli.command()
@click.argument('identifier', nargs=1)
@click.option('--account', '-a', help='Reblog as this user')
def reblog(identifier, account):
"""Reblog an existing post"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
post = Comment(identifier, blockchain_instance=stm)
tx = post.resteem(account=acc)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('follow', nargs=-1)
@click.option('--account', '-a', help='Follow from this account')
@click.option('--what', help='Follow these objects (defaults to ["blog"])', default=["blog"])
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def follow(follow, account, what, export):
"""Follow another account
Can be blog ignore blacklist unblacklist follow_blacklist unfollow_blacklist follow_muted unfollow_muted on HIVE
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if isinstance(what, str):
what = [what]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.follow(follow, what=what)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('mute', nargs=1)
@click.option('--account', '-a', help='Mute from this account')
@click.option('--what', help='Mute these objects (defaults to ["ignore"])', default=["ignore"])
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def mute(mute, account, what, export):
"""Mute another account"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if isinstance(what, str):
what = [what]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.follow(mute, what=what)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('unfollow', nargs=1)
@click.option('--account', '-a', help='UnFollow/UnMute from this account')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def unfollow(unfollow, account, export):
"""Unfollow/Unmute another account"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
tx = acc.unfollow(unfollow)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.option('--witness', help='Witness name')
@click.option('--maximum_block_size', help='Max block size')
@click.option('--account_creation_fee', help='Account creation fee')
@click.option('--sbd_interest_rate', help='SBD interest rate in percent')
@click.option('--hbd_interest_rate', help='HBD interest rate in percent')
@click.option('--url', help='Witness URL')
@click.option('--signing_key', help='Signing Key')
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def witnessupdate(witness, maximum_block_size, account_creation_fee, sbd_interest_rate, hbd_interest_rate, url, signing_key, export):
"""Change witness properties"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not witness:
witness = stm.config["default_account"]
if not unlock_wallet(stm):
return
witness = Witness(witness, blockchain_instance=stm)
props = witness["props"]
if account_creation_fee is not None:
props["account_creation_fee"] = str(
Amount("%.3f %s" % (float(account_creation_fee), stm.token_symbol), blockchain_instance=stm))
if maximum_block_size is not None:
props["maximum_block_size"] = int(maximum_block_size)
if sbd_interest_rate is not None:
props["sbd_interest_rate"] = int(float(sbd_interest_rate) * 100)
if hbd_interest_rate is not None:
props["hbd_interest_rate"] = int(float(hbd_interest_rate) * 100)
tx = witness.update(signing_key or witness["signing_key"], url or witness["url"], props)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def witnessdisable(witness, export):
"""Disable a witness"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not witness:
witness = stm.config["default_account"]
if not unlock_wallet(stm):
return
witness = Witness(witness, blockchain_instance=stm)
if not witness.is_active:
print("Cannot disable a disabled witness!")
return
props = witness["props"]
tx = witness.update('STM1111111111111111111111111111111114T1Anm', witness["url"], props)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
@click.argument('signing_key', nargs=1)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def witnessenable(witness, signing_key, export):
"""Enable a witness"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not witness:
witness = stm.config["default_account"]
if not unlock_wallet(stm):
return
witness = Witness(witness, blockchain_instance=stm)
props = witness["props"]
tx = witness.update(signing_key, witness["url"], props)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
@click.argument('pub_signing_key', nargs=1)
@click.option('--maximum_block_size', help='Max block size', default=65536)
@click.option('--account_creation_fee', help='Account creation fee', default=0.1)
@click.option('--sbd_interest_rate', help='SBD interest rate in percent', default=0.0)
@click.option('--hbd_interest_rate', help='HBD interest rate in percent', default=0.0)
@click.option('--url', help='Witness URL', default="")
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def witnesscreate(witness, pub_signing_key, maximum_block_size, account_creation_fee, sbd_interest_rate, hbd_interest_rate, url, export):
"""Create a witness"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm):
return
if stm.is_hive and stm.hardfork >= 24:
props = {
"account_creation_fee":
Amount("%.3f %s" % (float(account_creation_fee), stm.token_symbol), blockchain_instance=stm),
"maximum_block_size":
int(maximum_block_size),
"hbd_interest_rate":
int(hbd_interest_rate * 100)
}
else:
props = {
"account_creation_fee":
Amount("%.3f %s" % (float(account_creation_fee), stm.token_symbol), blockchain_instance=stm),
"maximum_block_size":
int(maximum_block_size),
"sbd_interest_rate":
int(sbd_interest_rate * 100)
}
tx = stm.witness_update(pub_signing_key, url, props, account=witness)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
@click.argument('wif', nargs=1)
@click.option('--account_creation_fee', help='Account creation fee (float)')
@click.option('--account_subsidy_budget', help='Account subisidy per block')
@click.option('--account_subsidy_decay', help='Per block decay of the account subsidy pool')
@click.option('--maximum_block_size', help='Max block size')
@click.option('--sbd_interest_rate', help='SBD interest rate in percent')
@click.option('--hbd_interest_rate', help='HBD interest rate in percent')
@click.option('--new_signing_key', help='Set new signing key (pubkey)')
@click.option('--url', help='Witness URL')
def witnessproperties(witness, wif, account_creation_fee, account_subsidy_budget, account_subsidy_decay, maximum_block_size, sbd_interest_rate, hbd_interest_rate, new_signing_key, url):
"""Update witness properties of witness WITNESS with the witness signing key WIF"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
# if not unlock_wallet(stm):
# return
props = {}
if account_creation_fee is not None:
props["account_creation_fee"] = Amount("%.3f %s" % (float(account_creation_fee), stm.token_symbol), blockchain_instance=stm)
if account_subsidy_budget is not None:
props["account_subsidy_budget"] = int(account_subsidy_budget)
if account_subsidy_decay is not None:
props["account_subsidy_decay"] = int(account_subsidy_decay)
if maximum_block_size is not None:
props["maximum_block_size"] = int(maximum_block_size)
if sbd_interest_rate is not None:
props["sbd_interest_rate"] = int(float(sbd_interest_rate) * 100)
if hbd_interest_rate is not None:
props["hbd_interest_rate"] = int(float(hbd_interest_rate) * 100)
if new_signing_key is not None:
props["new_signing_key"] = new_signing_key
if url is not None:
props["url"] = url
tx = stm.witness_set_properties(wif, witness, props)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
@click.argument('wif', nargs=1, required=False)
@click.option('--base', '-b', help='Set base manually, when not set the base is automatically calculated.')
@click.option('--quote', '-q', help='Steem quote manually, when not set the base is automatically calculated.')
@click.option('--support-peg', help='Supports peg adjusting the quote, is overwritten by --set-quote!', is_flag=True, default=False)
def witnessfeed(witness, wif, base, quote, support_peg):
"""Publish price feed for a witness"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if wif is None:
if not unlock_wallet(stm):
return
witness = Witness(witness, blockchain_instance=stm)
market = Market(blockchain_instance=stm)
use_hbd = False
if "hbd_exchange_rate" in witness:
use_hbd = True
old_base = witness["hbd_exchange_rate"]["base"]
old_quote = witness["hbd_exchange_rate"]["quote"]
last_published_price = Price(witness["hbd_exchange_rate"], blockchain_instance=stm)
else:
old_base = witness["sbd_exchange_rate"]["base"]
old_quote = witness["sbd_exchange_rate"]["quote"]
last_published_price = Price(witness["sbd_exchange_rate"], blockchain_instance=stm)
steem_usd = None
hive_usd = None
print("Old price %.3f (base: %s, quote %s)" % (float(last_published_price), old_base, old_quote))
if quote is None and not support_peg:
quote = Amount("1.000 %s" % stm.token_symbol, blockchain_instance=stm)
elif quote is None and not stm.is_hive:
latest_price = market.ticker()['latest']
if steem_usd is None:
steem_usd = market.steem_usd_implied()
sbd_usd = float(latest_price.as_base(stm.backed_token_symbol)) * steem_usd
quote = Amount(1. / sbd_usd, stm.token_symbol, blockchain_instance=stm)
elif quote is None and stm.is_hive:
latest_price = market.ticker()['latest']
if hive_usd is None:
hive_usd = market.hive_usd_implied()
hbd_usd = float(latest_price.as_base(stm.backed_token_symbol)) * hive_usd
quote = Amount(1. / hbd_usd, stm.token_symbol, blockchain_instance=stm)
else:
if str(quote[-5:]).upper() == stm.token_symbol:
quote = Amount(quote, blockchain_instance=stm)
else:
quote = Amount(quote, stm.token_symbol, blockchain_instance=stm)
if base is None and not stm.is_hive:
if steem_usd is None:
steem_usd = market.steem_usd_implied()
base = Amount(steem_usd, stm.backed_token_symbol, blockchain_instance=stm)
elif base is None and stm.is_hive:
if hive_usd is None:
hive_usd = market.hive_usd_implied()
base = Amount(hive_usd, stm.backed_token_symbol, blockchain_instance=stm)
else:
if str(quote[-3:]).upper() == stm.backed_token_symbol:
base = Amount(base, blockchain_instance=stm)
else:
base = Amount(base, stm.backed_token_symbol, blockchain_instance=stm)
new_price = Price(base=base, quote=quote, blockchain_instance=stm)
print("New price %.3f (base: %s, quote %s)" % (float(new_price), base, quote))
if wif is not None and use_hbd:
props = {"hbd_exchange_rate": new_price}
tx = stm.witness_set_properties(wif, witness["owner"], props)
elif wif is not None:
props = {"sbd_exchange_rate": new_price}
tx = stm.witness_set_properties(wif, witness["owner"], props)
else:
tx = witness.feed_publish(base, quote=quote)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('witness', nargs=1)
def witness(witness):
""" List witness information
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
witness = Witness(witness, blockchain_instance=stm)
witness_json = witness.json()
witness_schedule = stm.get_witness_schedule()
config = stm.get_config()
if "VIRTUAL_SCHEDULE_LAP_LENGTH2" in config:
lap_length = int(config["VIRTUAL_SCHEDULE_LAP_LENGTH2"])
elif "HIVE_VIRTUAL_SCHEDULE_LAP_LENGTH2" in config:
lap_length = int(config["HIVE_VIRTUAL_SCHEDULE_LAP_LENGTH2"])
else:
lap_length = int(config["STEEM_VIRTUAL_SCHEDULE_LAP_LENGTH2"])
rank = 0
active_rank = 0
found = False
witnesses = WitnessesRankedByVote(limit=250, blockchain_instance=stm)
vote_sum = witnesses.get_votes_sum()
for w in witnesses:
rank += 1
if w.is_active:
active_rank += 1
if w["owner"] == witness["owner"]:
found = True
break
virtual_time_to_block_num = int(witness_schedule["num_scheduled_witnesses"]) / (lap_length / (vote_sum + 1))
t = PrettyTable(["Key", "Value"])
t.align = "l"
for key in sorted(witness_json):
value = witness_json[key]
if key in ["props", "sbd_exchange_rate"]:
value = json.dumps(value, indent=4)
t.add_row([key, value])
if found:
t.add_row(["rank", rank])
t.add_row(["active_rank", active_rank])
virtual_diff = int(witness_json["virtual_scheduled_time"]) - int(witness_schedule['current_virtual_time'])
block_diff_est = virtual_diff * virtual_time_to_block_num
if active_rank > 20:
t.add_row(["virtual_time_diff", virtual_diff])
t.add_row(["block_diff_est", int(block_diff_est)])
next_block_s = int(block_diff_est) * 3
next_block_min = next_block_s / 60
next_block_h = next_block_min / 60
next_block_d = next_block_h / 24
time_diff_est = ""
if next_block_d > 1:
time_diff_est = "%.2f days" % next_block_d
elif next_block_h > 1:
time_diff_est = "%.2f hours" % next_block_h
elif next_block_min > 1:
time_diff_est = "%.2f minutes" % next_block_min
else:
time_diff_est = "%.2f seconds" % next_block_s
t.add_row(["time_diff_est", time_diff_est])
print(t)
@cli.command()
@click.argument('account', nargs=1, required=False)
@click.option('--limit', help='How many witnesses should be shown', default=100)
def witnesses(account, limit):
""" List witnesses
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if account:
account = Account(account, blockchain_instance=stm)
account_name = account["name"]
if account["proxy"] != "":
account_name = account["proxy"]
account_type = "Proxy"
else:
account_type = "Account"
witnesses = WitnessesVotedByAccount(account_name, blockchain_instance=stm)
print("%s: @%s (%d of 30)" % (account_type, account_name, len(witnesses)))
else:
witnesses = WitnessesRankedByVote(limit=limit, blockchain_instance=stm)
witnesses.printAsTable()
@cli.command()
@click.argument('account', nargs=1, required=False)
@click.option('--direction', default=None, help="in or out")
@click.option('--outgoing', '-o', help='Show outgoing votes', is_flag=True, default=False)
@click.option('--incoming', '-i', help='Show incoming votes', is_flag=True, default=False)
@click.option('--days', '-d', default=2., help="Limit shown vote history by this amount of days (default: 2)")
@click.option('--export', '-e', default=None, help="Export results to TXT-file")
def votes(account, direction, outgoing, incoming, days, export):
""" List outgoing/incoming account votes
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if direction is None and not incoming and not outgoing:
direction = "in"
utc = pytz.timezone('UTC')
limit_time = utc.localize(datetime.utcnow()) - timedelta(days=days)
out_votes_str = ""
in_votes_str = ""
if direction == "out" or outgoing:
votes = AccountVotes(account, start=limit_time, blockchain_instance=stm)
out_votes_str = votes.printAsTable(start=limit_time, return_str=True)
if direction == "in" or incoming:
account = Account(account, blockchain_instance=stm)
votes_list = []
for v in account.history(start=limit_time, only_ops=["vote"]):
vote = Vote(v, blockchain_instance=stm)
vote.refresh()
votes_list.append(vote)
votes = ActiveVotes(votes_list, blockchain_instance=stm)
in_votes_str = votes.printAsTable(votee=account["name"], return_str=True)
if export:
with open(export, 'w') as w:
w.write(out_votes_str)
w.write("\n")
w.write(in_votes_str)
else:
print(out_votes_str)
print(in_votes_str)
@cli.command()
@click.argument('authorperm', nargs=1, required=False)
@click.option('--account', '-a', help='Show only curation for this account')
@click.option('--limit', '-m', help='Show only the first minutes')
@click.option('--min-vote', '-v', help='Show only votes higher than the given value')
@click.option('--max-vote', '-w', help='Show only votes lower than the given value')
@click.option('--min-performance', '-x', help='Show only votes with performance higher than the given value in HBD/SBD')
@click.option('--max-performance', '-y', help='Show only votes with performance lower than the given value in HBD/SBD')
@click.option('--payout', default=None, help="Show the curation for a potential payout in SBD as float")
@click.option('--export', '-e', default=None, help="Export results to HTML-file")
@click.option('--short', '-s', is_flag=True, default=False, help="Show only Curation without sum")
@click.option('--length', '-l', help='Limits the permlink character length', default=None)
@click.option('--permlink', '-p', help='Show the permlink for each entry', is_flag=True, default=False)
@click.option('--title', '-t', help='Show the title for each entry', is_flag=True, default=False)
@click.option('--days', '-d', default=7., help="Limit shown rewards by this amount of days (default: 7), max is 7 days.")
def curation(authorperm, account, limit, min_vote, max_vote, min_performance, max_performance, payout, export, short, length, permlink, title, days):
""" Lists curation rewards of all votes for authorperm
When authorperm is empty or "all", the curation rewards
for all account votes are shown.
authorperm can also be a number. e.g. 5 is equivalent to
the fifth account vote in the given time duration (default is 7 days)
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
SP_symbol = "SP"
if stm.is_hive:
SP_symbol = "HP"
if authorperm is None:
authorperm = 'all'
if account is None and authorperm != 'all':
show_all_voter = True
else:
show_all_voter = False
if authorperm == 'all' or authorperm.isdigit():
if not account:
account = stm.config["default_account"]
utc = pytz.timezone('UTC')
limit_time = utc.localize(datetime.utcnow()) - timedelta(days=7)
votes = AccountVotes(account, start=limit_time, blockchain_instance=stm)
authorperm_list = [vote.authorperm for vote in votes]
if authorperm.isdigit():
if len(authorperm_list) < int(authorperm):
raise ValueError("Authorperm id must be lower than %d" % (len(authorperm_list) + 1))
authorperm_list = [authorperm_list[int(authorperm) - 1]]
all_posts = False
else:
all_posts = True
else:
authorperm_list = [authorperm]
all_posts = False
if (all_posts) and permlink:
t = PrettyTable(["Author", "permlink", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"])
t.align = "l"
elif (all_posts) and title:
t = PrettyTable(["Author", "permlink", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"])
t.align = "l"
elif all_posts:
t = PrettyTable(["Author", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"])
t.align = "l"
elif (export) and permlink:
t = PrettyTable(["Author", "permlink", "Voter", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"])
t.align = "l"
elif (export) and title:
t = PrettyTable(["Author", "permlink", "Voter", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"])
t.align = "l"
elif export:
t = PrettyTable(["Author", "Voter", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"])
t.align = "l"
else:
t = PrettyTable(["Voter", "Voting time", "Vote", "Early vote loss", "Curation", "Performance"])
t.align = "l"
index = 0
for authorperm in authorperm_list:
index += 1
comment = Comment(authorperm, blockchain_instance=stm)
if payout is not None and comment.is_pending():
payout = float(payout)
elif payout is not None:
payout = None
curation_rewards_SBD = comment.get_curation_rewards(pending_payout_SBD=True, pending_payout_value=payout)
curation_rewards_SP = comment.get_curation_rewards(pending_payout_SBD=False, pending_payout_value=payout)
rows = []
sum_curation = [0, 0, 0, 0]
max_curation = [0, 0, 0, 0, 0, 0]
highest_vote = [0, 0, 0, 0, 0, 0]
for vote in comment.get_votes():
vote_time = vote["time"]
vote_SBD = stm.rshares_to_token_backed_dollar(int(vote["rshares"]))
curation_SBD = curation_rewards_SBD["active_votes"][vote["voter"]]
curation_SP = curation_rewards_SP["active_votes"][vote["voter"]]
if vote_SBD > 0:
penalty = ((comment.get_curation_penalty(vote_time=vote_time)) * vote_SBD)
performance = (float(curation_SBD) / vote_SBD * 100)
else:
performance = 0
penalty = 0
vote_befor_min = (((vote_time) - comment["created"]).total_seconds() / 60)
sum_curation[0] += vote_SBD
sum_curation[1] += penalty
sum_curation[2] += float(curation_SP)
sum_curation[3] += float(curation_SBD)
row = [vote["voter"],
vote_befor_min,
vote_SBD,
penalty,
float(curation_SP),
performance]
rows.append(row)
sortedList = sorted(rows, key=lambda row: (row[1]), reverse=False)
new_row = []
new_row2 = []
voter = []
voter2 = []
if (all_posts or export) and permlink:
if length:
new_row = [comment.author, comment.permlink[:int(length)]]
else:
new_row = [comment.author, comment.permlink]
new_row2 = ["", ""]
elif (all_posts or export) and title:
if length:
new_row = [comment.author, comment.title[:int(length)]]
else:
new_row = [comment.author, comment.title]
new_row2 = ["", ""]
elif (all_posts or export):
new_row = [comment.author]
new_row2 = [""]
if not all_posts:
voter = [""]
voter2 = [""]
found_voter = False
for row in sortedList:
if limit is not None and row[1] > float(limit):
continue
if min_vote is not None and float(row[2]) < float(min_vote):
continue
if max_vote is not None and float(row[2]) > float(max_vote):
continue
if min_performance is not None and float(row[5]) < float(min_performance):
continue
if max_performance is not None and float(row[5]) > float(max_performance):
continue
if row[-1] > max_curation[-1]:
max_curation = row
if row[2] > highest_vote[2]:
highest_vote = row
if show_all_voter or account == row[0]:
if not all_posts:
voter = [row[0]]
if all_posts:
new_row[0] = "%d. %s" % (index, comment.author)
if not found_voter:
found_voter = True
t.add_row(new_row + voter + ["%.1f min" % row[1],
"%.3f %s" % (float(row[2]), stm.backed_token_symbol),
"%.3f %s" % (float(row[3]), stm.backed_token_symbol),
"%.3f %s" % (row[4], SP_symbol),
"%.1f %%" % (row[5])])
if len(authorperm_list) == 1:
new_row = new_row2
if not short and found_voter:
t.add_row(new_row2 + voter2 + ["", "", "", "", ""])
if sum_curation[0] > 0:
curation_sum_percentage = sum_curation[3] / sum_curation[0] * 100
else:
curation_sum_percentage = 0
sum_line = new_row2 + voter2
sum_line[-1] = "High. vote"
t.add_row(sum_line + ["%.1f min" % highest_vote[1],
"%.3f %s" % (float(highest_vote[2]), stm.backed_token_symbol),
"%.3f %s" % (float(highest_vote[3]), stm.backed_token_symbol),
"%.3f %s" % (highest_vote[4], SP_symbol),
"%.1f %%" % (highest_vote[5])])
sum_line[-1] = "High. Cur."
t.add_row(sum_line + ["%.1f min" % max_curation[1],
"%.3f %s" % (float(max_curation[2]), stm.backed_token_symbol),
"%.3f %s" % (float(max_curation[3]), stm.backed_token_symbol),
"%.3f %s" % (max_curation[4], SP_symbol),
"%.1f %%" % (max_curation[5])])
sum_line[-1] = "Sum"
t.add_row(sum_line + ["-",
"%.3f %s" % (sum_curation[0], stm.backed_token_symbol),
"%.3f %s" % (sum_curation[1], stm.backed_token_symbol),
"%.3f %s" % (sum_curation[2], SP_symbol),
"%.2f %%" % curation_sum_percentage])
if all_posts or export:
t.add_row(new_row2 + voter2 + ["-", "-", "-", "-", "-"])
if not (all_posts or export):
print("curation for %s" % (authorperm))
print(t)
if export:
with open(export, 'w') as w:
w.write(str(t.get_html_string()))
elif all_posts:
print("curation for @%s" % account)
print(t)
@cli.command()
@click.argument('accounts', nargs=-1, required=False)
@click.option('--only-sum', '-s', help='Show only the sum', is_flag=True, default=False)
@click.option('--post', '-p', help='Show post payout', is_flag=True, default=False)
@click.option('--comment', '-c', help='Show comments payout', is_flag=True, default=False)
@click.option('--curation', '-v', help='Shows curation', is_flag=True, default=False)
@click.option('--length', '-l', help='Limits the permlink character length', default=None)
@click.option('--author', '-a', help='Show the author for each entry', is_flag=True, default=False)
@click.option('--permlink', '-e', help='Show the permlink for each entry', is_flag=True, default=False)
@click.option('--title', '-t', help='Show the title for each entry', is_flag=True, default=False)
@click.option('--days', '-d', default=7., help="Limit shown rewards by this amount of days (default: 7)")
def rewards(accounts, only_sum, post, comment, curation, length, author, permlink, title, days):
""" Lists received rewards
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not accounts:
accounts = [stm.config["default_account"]]
if not comment and not curation and not post:
post = True
permlink = True
if days < 0:
days = 1
utc = pytz.timezone('UTC')
now = utc.localize(datetime.utcnow())
limit_time = now - timedelta(days=days)
for account in accounts:
sum_reward = [0, 0, 0, 0, 0]
account = Account(account, blockchain_instance=stm)
median_price = Price(stm.get_current_median_history(), blockchain_instance=stm)
m = Market(blockchain_instance=stm)
latest = m.ticker()["latest"]
if author and permlink:
t = PrettyTable(["Author", "Permlink", "Payout", stm.backed_token_symbol, "%sP + %s" % (stm.token_symbol[0], stm.token_symbol), "Liquid USD", "Invested USD"])
elif author and title:
t = PrettyTable(["Author", "Title", "Payout", stm.backed_token_symbol, "%sP + %s" % (stm.token_symbol[0], stm.token_symbol), "Liquid USD", "Invested USD"])
elif author:
t = PrettyTable(["Author", "Payout", stm.backed_token_symbol, "%sP + %s" % (stm.token_symbol[0], stm.token_symbol), "Liquid USD", "Invested USD"])
elif not author and permlink:
t = PrettyTable(["Permlink", "Payout", stm.backed_token_symbol, "%sP + %s" % (stm.token_symbol[0], stm.token_symbol), "Liquid USD", "Invested USD"])
elif not author and title:
t = PrettyTable(["Title", "Payout", stm.backed_token_symbol, "%sP + %s" % (stm.token_symbol[0], stm.token_symbol), "Liquid USD", "Invested USD"])
else:
t = PrettyTable(["Received", stm.backed_token_symbol, "%sP + %s" % (stm.token_symbol[0], stm.token_symbol), "Liquid USD", "Invested USD"])
t.align = "l"
rows = []
start_op = account.estimate_virtual_op_num(limit_time)
if start_op > 0:
start_op -= 1
only_ops = ['author_reward', 'curation_reward']
progress_length = (account.virtual_op_count() - start_op) / 1000
with click.progressbar(account.history(start=start_op, use_block_num=False, only_ops=only_ops), length=progress_length) as comment_hist:
for v in comment_hist:
if not curation and v["type"] == "curation_reward":
continue
if not post and not comment and v["type"] == "author_reward":
continue
if v["type"] == "author_reward":
c = Comment(v, blockchain_instance=stm)
try:
c.refresh()
except exceptions.ContentDoesNotExistsException:
continue
if not post and not c.is_comment():
continue
if not comment and c.is_comment():
continue
if "sbd_payout" in v:
payout_SBD = Amount(v["sbd_payout"], blockchain_instance=stm)
payout_STEEM = Amount(v["steem_payout"], blockchain_instance=stm)
else:
payout_SBD = Amount(v["hbd_payout"], blockchain_instance=stm)
payout_STEEM = Amount(v["hive_payout"], blockchain_instance=stm)
sum_reward[0] += float(payout_SBD)
sum_reward[1] += float(payout_STEEM)
payout_SP = stm.vests_to_token_power(Amount(v["vesting_payout"], blockchain_instance=stm))
sum_reward[2] += float(payout_SP)
liquid_USD = float(payout_SBD) / float(latest) * float(median_price) + float(payout_STEEM) * float(median_price)
sum_reward[3] += liquid_USD
invested_USD = float(payout_SP) * float(median_price)
sum_reward[4] += invested_USD
if c.is_comment():
permlink_row = c.parent_permlink
else:
if title:
permlink_row = c.title
else:
permlink_row = c.permlink
rows.append([c["author"],
permlink_row,
((now - formatTimeString(v["timestamp"])).total_seconds() / 60 / 60 / 24),
(payout_SBD),
(payout_STEEM),
(payout_SP),
(liquid_USD),
(invested_USD)])
elif v["type"] == "curation_reward":
reward = Amount(v["reward"], blockchain_instance=stm)
payout_SP = stm.vests_to_token_power(reward)
liquid_USD = 0
invested_USD = float(payout_SP) * float(median_price)
sum_reward[2] += float(payout_SP)
sum_reward[4] += invested_USD
if title:
c = Comment(construct_authorperm(v["comment_author"], v["comment_permlink"]), blockchain_instance=stm)
permlink_row = c.title
else:
permlink_row = v["comment_permlink"]
rows.append([v["comment_author"],
permlink_row,
((now - formatTimeString(v["timestamp"])).total_seconds() / 60 / 60 / 24),
0.000,
0.000,
payout_SP,
(liquid_USD),
(invested_USD)])
sortedList = sorted(rows, key=lambda row: (row[2]), reverse=False)
if only_sum:
sortedList = []
for row in sortedList:
if length:
permlink_row = row[1][:int(length)]
else:
permlink_row = row[1]
if author and (permlink or title):
t.add_row([row[0],
permlink_row,
"%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % (float(row[4]) + float(row[5])),
"%.2f $" % (row[6]),
"%.2f $" % (row[7])])
elif author and not (permlink or title):
t.add_row([row[0],
"%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % (float(row[4]) + float(row[5])),
"%.2f $" % (row[5]),
"%.2f $" % (row[6])])
elif not author and (permlink or title):
t.add_row([permlink_row,
"%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % (float(row[4]) + float(row[5])),
"%.2f $" % (row[5]),
"%.2f $" % (row[6])])
else:
t.add_row(["%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % (float(row[4]) + float(row[5])),
"%.2f $" % (row[5]),
"%.2f $" % (row[6])])
if author and (permlink or title):
if not only_sum:
t.add_row(["", "", "", "", "", "", ""])
t.add_row(["Sum",
"-",
"-",
"%.2f %s" % (sum_reward[0], stm.backed_token_symbol),
"%.2f %sP" % (sum_reward[1] + sum_reward[2], stm.token_symbol[0]),
"%.2f $" % (sum_reward[3]),
"%.2f $" % (sum_reward[4])])
elif not author and not (permlink or title):
t.add_row(["", "", "", "", ""])
t.add_row(["Sum",
"%.2f %s" % (sum_reward[0], stm.backed_token_symbol),
"%.2f %sP" % (sum_reward[1] + sum_reward[2], stm.token_symbol[0]),
"%.2f $" % (sum_reward[2]),
"%.2f $" % (sum_reward[3])])
else:
t.add_row(["", "", "", "", "", ""])
t.add_row(["Sum",
"-",
"%.2f %s" % (sum_reward[0], stm.backed_token_symbol),
"%.2f %sP" % (sum_reward[1] + sum_reward[2], stm.token_symbol[0]),
"%.2f $" % (sum_reward[3]),
"%.2f $" % (sum_reward[4])])
message = "\nShowing "
if post:
if comment + curation == 0:
message += "post "
elif comment + curation == 1:
message += "post and "
else:
message += "post, "
if comment:
if curation == 0:
message += "comment "
else:
message += "comment and "
if curation:
message += "curation "
message += "rewards for @%s" % account.name
print(message)
print(t)
@cli.command()
@click.argument('accounts', nargs=-1, required=False)
@click.option('--only-sum', '-s', help='Show only the sum', is_flag=True, default=False)
@click.option('--post', '-p', help='Show pending post payout', is_flag=True, default=False)
@click.option('--comment', '-c', help='Show pending comments payout', is_flag=True, default=False)
@click.option('--curation', '-v', help='Shows pending curation', is_flag=True, default=False)
@click.option('--length', '-l', help='Limits the permlink character length', default=None)
@click.option('--author', '-a', help='Show the author for each entry', is_flag=True, default=False)
@click.option('--permlink', '-e', help='Show the permlink for each entry', is_flag=True, default=False)
@click.option('--title', '-t', help='Show the title for each entry', is_flag=True, default=False)
@click.option('--days', '-d', default=7., help="Limit shown rewards by this amount of days (default: 7), max is 7 days.")
@click.option('--from', '-f', '_from', default=0., help="Start day from which on rewards are shown (default: 0), max is 7 days.")
def pending(accounts, only_sum, post, comment, curation, length, author, permlink, title, days, _from):
""" Lists pending rewards
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not accounts:
accounts = [stm.config["default_account"]]
if not comment and not curation and not post:
post = True
permlink = True
if days < 0:
days = 1
if days > 7:
days = 7
if _from < 0:
_from = 0
if _from > 7:
_from = 7
if _from + days > 7:
days = 7 - _from
sp_symbol = "SP"
if stm.is_hive:
sp_symbol = "HP"
utc = pytz.timezone('UTC')
max_limit_time = utc.localize(datetime.utcnow()) - timedelta(days=7)
limit_time = utc.localize(datetime.utcnow()) - timedelta(days=_from + days)
start_time = utc.localize(datetime.utcnow()) - timedelta(days=_from)
for account in accounts:
sum_reward = [0, 0, 0, 0]
account = Account(account, blockchain_instance=stm)
median_price = Price(stm.get_current_median_history(), blockchain_instance=stm)
m = Market(blockchain_instance=stm)
latest = m.ticker()["latest"]
if author and permlink:
t = PrettyTable(["Author", "Permlink", "Cashout", stm.backed_token_symbol, sp_symbol, "Liquid USD", "Invested USD"])
elif author and title:
t = PrettyTable(["Author", "Title", "Cashout", stm.backed_token_symbol, sp_symbol, "Liquid USD", "Invested USD"])
elif author:
t = PrettyTable(["Author", "Cashout", stm.backed_token_symbol, sp_symbol, "Liquid USD", "Invested USD"])
elif not author and permlink:
t = PrettyTable(["Permlink", "Cashout", stm.backed_token_symbol, sp_symbol, "Liquid USD", "Invested USD"])
elif not author and title:
t = PrettyTable(["Title", "Cashout", stm.backed_token_symbol, sp_symbol, "Liquid USD", "Invested USD"])
else:
t = PrettyTable(["Cashout", stm.backed_token_symbol, sp_symbol, "Liquid USD", "Invested USD"])
t.align = "l"
rows = []
c_list = {}
start_op = account.estimate_virtual_op_num(limit_time)
stop_op = account.estimate_virtual_op_num(start_time)
if start_op > 0:
start_op -= 1
progress_length = (stop_op - start_op) / 1000
with click.progressbar(map(Comment, account.history(start=start_op, stop=stop_op, use_block_num=False, only_ops=["comment"])), length=progress_length) as comment_hist:
for v in comment_hist:
try:
v.refresh()
except exceptions.ContentDoesNotExistsException:
continue
author_reward = v.get_author_rewards()
if float(author_reward["total_payout_SBD"]) < 0.001:
continue
if v.permlink in c_list:
continue
c_list[v.permlink] = 1
if not v.is_pending():
continue
if not post and not v.is_comment():
continue
if not comment and v.is_comment():
continue
if v["author"] != account["name"]:
continue
payout_SBD = author_reward["payout_SBD"]
sum_reward[0] += float(payout_SBD)
payout_SP = author_reward["payout_SP"]
sum_reward[1] += float(payout_SP)
liquid_USD = float(author_reward["payout_SBD"]) / float(latest) * float(median_price)
sum_reward[2] += liquid_USD
invested_USD = float(author_reward["payout_SP"]) * float(median_price)
sum_reward[3] += invested_USD
if v.is_comment():
permlink_row = v.permlink
else:
if title:
permlink_row = v.title
else:
permlink_row = v.permlink
rows.append([v["author"],
permlink_row,
((v["created"] - max_limit_time).total_seconds() / 60 / 60 / 24),
(payout_SBD),
(payout_SP),
(liquid_USD),
(invested_USD)])
if curation:
votes = AccountVotes(account, start=limit_time, stop=start_time, blockchain_instance=stm)
for vote in votes:
authorperm = construct_authorperm(vote["author"], vote["permlink"])
try:
c = Comment(authorperm, blockchain_instance=stm)
except exceptions.ContentDoesNotExistsException:
continue
rewards = c.get_curation_rewards()
if not rewards["pending_rewards"]:
continue
days_to_payout = ((c["created"] - max_limit_time).total_seconds() / 60 / 60 / 24)
if days_to_payout < 0:
continue
payout_SP = rewards["active_votes"][account["name"]]
liquid_USD = 0
invested_USD = float(payout_SP) * float(median_price)
sum_reward[1] += float(payout_SP)
sum_reward[3] += invested_USD
if title:
permlink_row = c.title
else:
permlink_row = c.permlink
rows.append([c["author"],
permlink_row,
days_to_payout,
0.000,
payout_SP,
(liquid_USD),
(invested_USD)])
sortedList = sorted(rows, key=lambda row: (row[2]), reverse=True)
if only_sum:
sortedList = []
for row in sortedList:
if length:
permlink_row = row[1][:int(length)]
else:
permlink_row = row[1]
if author and (permlink or title):
t.add_row([row[0],
permlink_row,
"%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % float(row[4]),
"%.2f $" % (row[5]),
"%.2f $" % (row[6])])
elif author and not (permlink or title):
t.add_row([row[0],
"%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % float(row[4]),
"%.2f $" % (row[5]),
"%.2f $" % (row[6])])
elif not author and (permlink or title):
t.add_row([permlink_row,
"%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % float(row[4]),
"%.2f $" % (row[5]),
"%.2f $" % (row[6])])
else:
t.add_row(["%.1f days" % row[2],
"%.3f" % float(row[3]),
"%.3f" % float(row[4]),
"%.2f $" % (row[5]),
"%.2f $" % (row[6])])
if author and (permlink or title):
if not only_sum:
t.add_row(["", "", "", "", "", "", ""])
t.add_row(["Sum",
"-",
"-",
"%.2f %s" % (sum_reward[0], stm.backed_token_symbol),
"%.2f %s" % (sum_reward[1], sp_symbol),
"%.2f $" % (sum_reward[2]),
"%.2f $" % (sum_reward[3])])
elif not author and not (permlink or title):
t.add_row(["", "", "", "", ""])
t.add_row(["Sum",
"%.2f %s" % (sum_reward[0], stm.backed_token_symbol),
"%.2f %s" % (sum_reward[1], sp_symbol),
"%.2f $" % (sum_reward[2]),
"%.2f $" % (sum_reward[3])])
else:
t.add_row(["", "", "", "", "", ""])
t.add_row(["Sum",
"-",
"%.2f %s" % (sum_reward[0], stm.backed_token_symbol),
"%.2f %s" % (sum_reward[1], sp_symbol),
"%.2f $" % (sum_reward[2]),
"%.2f $" % (sum_reward[3])])
message = "\nShowing pending "
if post:
if comment + curation == 0:
message += "post "
elif comment + curation == 1:
message += "post and "
else:
message += "post, "
if comment:
if curation == 0:
message += "comment "
else:
message += "comment and "
if curation:
message += "curation "
message += "rewards for @%s" % account.name
print(message)
print(t)
@cli.command()
@click.argument('account', nargs=1, required=False)
@click.option('--reward_steem', help='Amount of STEEM/HIVE you would like to claim', default=0)
@click.option('--reward_sbd', help='Amount of SBD/HBD you would like to claim', default=0)
@click.option('--reward_vests', help='Amount of VESTS you would like to claim', default=0)
@click.option('--claim_all_steem', help='Claim all STEEM/HIVE, overwrites reward_steem', is_flag=True)
@click.option('--claim_all_sbd', help='Claim all SBD/HBD, overwrites reward_sbd', is_flag=True)
@click.option('--claim_all_vests', help='Claim all VESTS, overwrites reward_vests', is_flag=True)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def claimreward(account, reward_steem, reward_sbd, reward_vests, claim_all_steem, claim_all_sbd, claim_all_vests, export):
"""Claim reward balances
By default, this will claim ``all`` outstanding balances.
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
acc = Account(account, blockchain_instance=stm)
r = acc.balances["rewards"]
if len(r) == 3 and r[0].amount + r[1].amount + r[2].amount == 0:
print("Nothing to claim.")
return
elif len(r) == 2 and r[0].amount + r[1].amount:
print("Nothing to claim.")
return
if not unlock_wallet(stm):
return
if claim_all_steem:
reward_steem = r[0]
if claim_all_sbd:
reward_sbd = r[1]
if claim_all_vests:
reward_vests = r[2]
tx = acc.claim_reward_balance(reward_steem, reward_sbd, reward_vests)
if stm.unsigned and stm.nobroadcast and stm.steemconnect is not None:
tx = stm.steemconnect.url_from_tx(tx)
elif stm.unsigned and stm.nobroadcast and stm.hivesigner is not None:
tx = stm.hivesigner.url_from_tx(tx)
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('jsonid', nargs=1)
@click.argument('json_data', nargs=-1)
@click.option('--account', '-a', help='The account which broadcasts the custom_json')
@click.option('--active', '-t', help='When set, the active key is used for broadcasting', is_flag=True, default=False)
@click.option('--export', '-e', help='When set, transaction is stored in a file')
def customjson(jsonid, json_data, account, active, export):
"""Broadcasts a custom json
First parameter is the cusom json id, the second field is a json file or a json key value combination
e.g. beempy customjson -a holger80 dw-heist username holger80 amount 100
"""
if jsonid is None:
print("First argument must be the custom_json id")
if json_data is None:
print("Second argument must be the json_data, can be a string or a file name.")
data = import_custom_json(jsonid, json_data)
if data is None:
return
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if not unlock_wallet(stm):
return
acc = Account(account, blockchain_instance=stm)
if active:
tx = stm.custom_json(jsonid, data, required_auths=[account])
else:
tx = stm.custom_json(jsonid, data, required_posting_auths=[account])
export_trx(tx, export)
tx = json.dumps(tx, indent=4)
print(tx)
@cli.command()
@click.argument('blocknumber', nargs=1, required=False)
@click.option('--trx', '-t', help='Show only one transaction number', default=None)
@click.option('--use-api', '-u', help='Uses the get_potential_signatures api call', is_flag=True, default=False)
def verify(blocknumber, trx, use_api):
"""Returns the public signing keys for a block"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
b = Blockchain(blockchain_instance=stm)
i = 0
if not blocknumber:
blocknumber = b.get_current_block_num()
try:
int(blocknumber)
block = Block(blocknumber, blockchain_instance=stm)
if trx is not None:
i = int(trx)
trxs = [block.json_transactions[int(trx)]]
else:
trxs = block.json_transactions
except Exception:
trxs = [b.get_transaction(blocknumber)]
blocknumber = trxs[0]["block_num"]
wallet = Wallet(blockchain_instance=stm)
t = PrettyTable(["trx", "Signer key", "Account"])
t.align = "l"
if not use_api:
from beembase.signedtransactions import Signed_Transaction
for trx in trxs:
if not use_api:
# trx is now identical to the output of get_transaction
# This is just for testing porpuse
if True:
signed_tx = Signed_Transaction(trx.copy())
else:
tx = b.get_transaction(trx["transaction_id"])
signed_tx = Signed_Transaction(tx)
public_keys = []
for key in signed_tx.verify(chain=stm.chain_params, recover_parameter=True):
public_keys.append(format(Base58(key, prefix=stm.prefix), stm.prefix))
else:
tx = TransactionBuilder(tx=trx, blockchain_instance=stm)
public_keys = tx.get_potential_signatures()
accounts = []
empty_public_keys = []
for key in public_keys:
account = wallet.getAccountFromPublicKey(key)
if account is None:
empty_public_keys.append(key)
else:
accounts.append(account)
new_public_keys = []
for key in public_keys:
if key not in empty_public_keys or use_api:
new_public_keys.append(key)
if len(new_public_keys) == 0:
for key in public_keys:
new_public_keys.append(key)
if isinstance(new_public_keys, list) and len(new_public_keys) == 1:
new_public_keys = new_public_keys[0]
else:
new_public_keys = json.dumps(new_public_keys, indent=4)
if isinstance(accounts, list) and len(accounts) == 1:
accounts = accounts[0]
else:
accounts = json.dumps(accounts, indent=4)
t.add_row(["%d" % i, new_public_keys, accounts])
i += 1
print(t)
@cli.command()
def chainconfig():
""" Prints chain config in a table"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
chain_config = stm.get_config()
t = PrettyTable(["Key", "Value"])
t.align = "l"
for key in chain_config:
if isinstance(chain_config[key], dict) and 'amount' in chain_config[key]:
t.add_row([key, str(Amount(chain_config[key], blockchain_instance=stm))])
else:
t.add_row([key, chain_config[key]])
print(t)
@cli.command()
@click.argument('objects', nargs=-1)
def info(objects):
""" Show basic blockchain info
General information about the blockchain, a block, an account,
a post/comment and a public key
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not objects:
t = PrettyTable(["Key", "Value"])
t.align = "l"
info = stm.get_dynamic_global_properties()
median_price = stm.get_current_median_history()
token_per_mvest = stm.get_token_per_mvest()
chain_props = stm.get_chain_properties()
try:
price = (Amount(median_price["base"], blockchain_instance=stm).amount / Amount(median_price["quote"], blockchain_instance=stm).amount)
except:
price = None
for key in info:
if isinstance(info[key], dict) and 'amount' in info[key]:
t.add_row([key, str(Amount(info[key], blockchain_instance=stm))])
else:
t.add_row([key, info[key]])
t.add_row(["%s per mvest" % stm.token_symbol, token_per_mvest])
if price is not None:
t.add_row(["internal price", price])
t.add_row(["account_creation_fee", str(Amount(chain_props["account_creation_fee"], blockchain_instance=stm))])
print(t.get_string(sortby="Key"))
# Block
for obj in objects:
if re.match(r"^[0-9-]*$", obj) or re.match(r"^-[0-9]*$", obj) or re.match(r"^[0-9-]*:[0-9]", obj) or re.match(r"^[0-9-]*:-[0-9]", obj):
tran_nr = ''
if re.match(r"^[0-9-]*:[0-9-]", obj):
obj, tran_nr = obj.split(":")
if int(obj) < 1:
b = Blockchain(blockchain_instance=stm)
block_number = b.get_current_block_num() + int(obj) - 1
else:
block_number = obj
block = Block(block_number, blockchain_instance=stm)
if block:
t = PrettyTable(["Key", "Value"])
t.align = "l"
block_json = block.json()
for key in sorted(block_json):
value = block_json[key]
if key == "transactions" and not bool(tran_nr):
t.add_row(["Nr. of transactions", len(value)])
elif key == "transactions" and bool(tran_nr):
if int(tran_nr) < 0:
tran_nr = len(value) + int(tran_nr)
else:
tran_nr = int(tran_nr)
if len(value) > tran_nr - 1 and tran_nr > -1:
t_value = json.dumps(value[tran_nr], indent=4)
t.add_row(["transaction %d/%d" % (tran_nr, len(value)), t_value])
elif key == "transaction_ids" and not bool(tran_nr):
t.add_row(["Nr. of transaction_ids", len(value)])
elif key == "transaction_ids" and bool(tran_nr):
if int(tran_nr) < 0:
tran_nr = len(value) + int(tran_nr)
else:
tran_nr = int(tran_nr)
if len(value) > tran_nr - 1 and tran_nr > -1:
t.add_row(["transaction_id %d/%d" % (int(tran_nr), len(value)), value[tran_nr]])
else:
t.add_row([key, value])
print(t)
else:
print("Block number %s unknown" % obj)
elif re.match(r"^[a-zA-Z0-9\-\._]{2,16}$", obj):
account = Account(obj, blockchain_instance=stm)
t = PrettyTable(["Key", "Value"])
t.align = "l"
t._max_width = {"Value" : 80}
account_json = account.json()
for key in sorted(account_json):
value = account_json[key]
if key == "json_metadata":
value = json.dumps(json.loads(value or "{}"), indent=4)
elif key in ["posting", "witness_votes", "active", "owner"]:
value = json.dumps(value, indent=4)
elif key == "reputation" and int(value) > 0:
value = int(value)
rep = account.rep
value = "{:.2f} ({:d})".format(rep, value)
elif isinstance(value, dict) and "asset" in value:
value = str(account[key])
t.add_row([key, value])
print(t)
# witness available?
try:
witness = Witness(obj, blockchain_instance=stm)
witness_json = witness.json()
t = PrettyTable(["Key", "Value"])
t.align = "l"
for key in sorted(witness_json):
value = witness_json[key]
if key in ["props", "sbd_exchange_rate"]:
value = json.dumps(value, indent=4)
t.add_row([key, value])
print(t)
except exceptions.WitnessDoesNotExistsException as e:
print(str(e))
# Public Key
elif re.match(r"^" + stm.prefix + ".{48,55}$", obj):
account = stm.wallet.getAccountFromPublicKey(obj)
if account:
account = Account(account, blockchain_instance=stm)
key_type = stm.wallet.getKeyType(account, obj)
t = PrettyTable(["Account", "Key_type"])
t.align = "l"
t._max_width = {"Value" : 80}
t.add_row([account["name"], key_type])
print(t)
else:
print("Public Key %s not known" % obj)
# Post identifier
elif re.match(r".*@.{3,16}/.*$", obj):
post = Comment(obj, blockchain_instance=stm)
post_json = post.json()
if post_json:
t = PrettyTable(["Key", "Value"])
t.align = "l"
t._max_width = {"Value" : 80}
for key in sorted(post_json):
if key in ["body", "active_votes"]:
value = "not shown"
else:
value = post_json[key]
if (key in ["json_metadata"]):
value = json.loads(value)
value = json.dumps(value, indent=4)
elif (key in ["tags", "active_votes"]):
value = json.dumps(value, indent=4)
t.add_row([key, value])
print(t)
else:
print("Post now known" % obj)
elif re.match(r"^[a-zA-Z0-9\_]{40}$", obj):
b = Blockchain(blockchain_instance=stm)
from beemapi.exceptions import UnknownTransaction
try:
trx = b.get_transaction(obj)
except UnknownTransaction:
print("%s is unknown!" % obj)
return
t = PrettyTable(["Key", "Value"])
t.align = "l"
t._max_width = {"Value" : 80}
for key in trx:
value = trx[key]
if key in ["operations", "signatures"]:
value = json.dumps(value, indent=4)
t.add_row([key, value])
print(t)
else:
print("Couldn't identify object to read")
@cli.command()
@click.argument('account', nargs=1, required=False)
@click.option('--signing-account', '-s', help='Signing account, when empty account is used.')
def userdata(account, signing_account):
""" Get the account's email address and phone number.
The request has to be signed by the requested account or an admin account.
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm):
return
if not account:
if "default_account" in stm.config:
account = stm.config["default_account"]
account = Account(account, blockchain_instance=stm)
if signing_account is not None:
signing_account = Account(signing_account, blockchain_instance=stm)
c = Conveyor(blockchain_instance=stm)
user_data = c.get_user_data(account, signing_account=signing_account)
t = PrettyTable(["Key", "Value"])
t.align = "l"
for key in user_data:
# hide internal config data
t.add_row([key, user_data[key]])
print(t)
@cli.command()
@click.argument('account', nargs=1, required=False)
@click.option('--limit', '-l', help='Defines how many ops should be printed (default=10)', default=10)
@click.option('--sort', '-s', help='Defines the printing sorting, 1 ->, -1 <- (default=-1)', default=-1)
@click.option('--max-length', '-m', help='Maximum printed string length', default=80)
@click.option('--virtual-ops', '-v', help='When set, virtual ops are also shown', is_flag=True, default=False)
@click.option('--only-ops', '-o', help='Included komma seperated list of op types, which limits the shown operations. When set, virtual-ops is always set to true')
@click.option('--exclude-ops', '-e', help='Excluded komma seperated list of op types, which limits the shown operations.')
@click.option('--json-file', '-j', help='When set, the results are written into a json file')
def history(account, limit, sort, max_length, virtual_ops, only_ops, exclude_ops, json_file):
""" Returns account history operations as table
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
if "default_account" in stm.config:
account = stm.config["default_account"]
account = Account(account, blockchain_instance=stm)
t = PrettyTable(["Index","Type", "Hist op"])
t.align = "l"
t._max_width = {"Hist op" : max_length}
cnt = 0
batch_size = 1000
if batch_size > int(limit) + 1 and int(limit) > 0:
batch_size = int(limit) + 1
if only_ops is None:
only_ops = []
else:
only_ops = only_ops.split(",")
if exclude_ops is None:
exclude_ops = []
else:
exclude_ops = exclude_ops.split(",")
if len(only_ops) > 0:
virtual_ops = True
data = []
if int(sort) == -1:
hist = account.history_reverse(batch_size=batch_size, only_ops=only_ops, exclude_ops=exclude_ops)
else:
hist = account.history(batch_size=batch_size, only_ops=only_ops, exclude_ops=exclude_ops)
for h in hist:
if h["virtual_op"] == 1 and not virtual_ops:
continue
cnt += 1
if cnt > int(limit) and int(limit) > 0:
break
if json_file is not None:
data.append(h)
else:
# if key in ["operations", "signatures"]:
index = h.pop("index")
op_type = h.pop("type")
h.pop("trx_in_block")
h.pop("op_in_trx")
h.pop("virtual_op")
h.pop("_id")
if h["trx_id"] == "0000000000000000000000000000000000000000":
h.pop("trx_id")
for key in h:
if isinstance(h[key], dict) and "nai" in h[key]:
h[key] = str(Amount(h[key], blockchain_instance=stm))
if key == "json" or key == "json_metadata" and h[key] is not None and h[key] != "":
h[key] = json.loads(h[key])
value = json.dumps(h, indent=4)
t.add_row([str(index), op_type, value])
if json_file is not None:
with open(json_file, "w", encoding="utf-8") as f:
json.dump(data, f)
else:
print(t)
@cli.command()
@click.argument('account', nargs=1, required=False)
@click.option('--signing-account', '-s', help='Signing account, when empty account is used.')
def featureflags(account, signing_account):
""" Get the account's feature flags.
The request has to be signed by the requested account or an admin account.
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not unlock_wallet(stm):
return
if not account:
if "default_account" in stm.config:
account = stm.config["default_account"]
account = Account(account, blockchain_instance=stm)
if signing_account is not None:
signing_account = Account(signing_account, blockchain_instance=stm)
c = Conveyor(blockchain_instance=stm)
user_data = c.get_feature_flags(account, signing_account=signing_account)
t = PrettyTable(["Key", "Value"])
t.align = "l"
for key in user_data:
# hide internal config data
t.add_row([key, user_data[key]])
print(t)
@cli.command()
@click.option('--block', '-b', help='Select a block number, when skipped the latest block is used.', default=None)
@click.option('--trx-id', '-t', help='Select a trx-id, When skipped, the latest one is used.', default=None)
@click.option('--draws', '-d', help='Number of draws (default = 1)', default=1)
@click.option('--participants', '-p', help='Number of participants or file name including participants (one participant per line), (default = 100)', default="100")
@click.option('--hashtype', '-h', help='Can be md5, sha256, sha512 (default = sha256)', default="sha256")
@click.option('--separator', '-s', help='Is used for sha256 and sha512 to seperate the draw number from the seed (default = ,)', default=",")
@click.option('--account', '-a', help='The account which broadcasts the reply')
@click.option('--reply', '-r', help='Parent post/comment authorperm. When set, the results will be broadcasted as reply to this authorperm.', default=None)
@click.option('--without-replacement', '-w', help='When set, numbers are drawed without replacement.', is_flag=True, default=False)
@click.option('--markdown', '-m', help='When set, results are returned in markdown format', is_flag=True, default=False)
def draw(block, trx_id, draws, participants, hashtype, separator, account, reply, without_replacement, markdown):
""" Generate pseudo-random numbers based on trx id, block id and previous block id.
When using --reply, the result is directly broadcasted as comment
"""
stm = shared_blockchain_instance()
if stm.rpc is not None:
stm.rpc.rpcconnect()
if not account:
account = stm.config["default_account"]
if reply is not None:
if not unlock_wallet(stm):
return
reply_comment = Comment(reply, blockchain_instance=stm)
if block is not None and block != "":
block = Block(int(block), blockchain_instance=stm)
else:
blockchain = Blockchain(blockchain_instance=stm)
block = blockchain.get_current_block()
data = None
for trx in block.transactions:
if trx["transaction_id"] == trx_id:
data = trx
elif trx_id is None:
trx_id = trx["transaction_id"]
data = trx
if trx_id is None:
trx_id = "0"
if os.path.exists(participants):
with open(participants) as f:
content = f.read()
if content.find(",") > 0:
participants_list = content.split(",")
else:
participants_list = content.split("\n")
if participants_list[-1] == "":
participants_list = participants_list[:-1]
participants = len(participants_list)
else:
participants = int(participants)
participants_list = []
if without_replacement:
assert draws <= participants
trx = data["operations"][0]["value"]
if hashtype == "md5":
seed = hashlib.md5((trx_id + block["block_id"] + block["previous"]).encode()).hexdigest()
elif hashtype == "sha256":
seed = hashlib.sha256((trx_id + block["block_id"] + block["previous"]).encode()).hexdigest()
elif hashtype == "sha512":
seed = hashlib.sha512((trx_id + block["block_id"] + block["previous"]).encode()).hexdigest()
random.seed(a=seed, version=2)
t = PrettyTable(["Key", "Value"])
t.align = "l"
t.add_row(["block number", block["id"]])
t.add_row(["trx id", trx_id])
t.add_row(["block id", block["block_id"]])
t.add_row(["previous", block["previous"]])
t.add_row(["hash type", hashtype])
t.add_row(["draws", draws])
t.add_row(["participants", participants])
draw_list = [x + 1 for x in range(participants)]
results = []
for i in range(int(draws)):
if hashtype == "md5":
number = int(random.random() * len(draw_list))
elif hashtype == "sha256":
seed = hashlib.sha256((trx_id + block["block_id"] + block["previous"] + separator +str(i + 1)).encode()).digest()
bigRand = int.from_bytes(seed, 'big')
number = bigRand % (len(draw_list))
elif hashtype == "sha512":
seed = hashlib.sha512((trx_id + block["block_id"] + block["previous"] + separator +str(i + 1)).encode()).digest()
bigRand = int.from_bytes(seed, 'big')
number = bigRand % (len(draw_list))
results.append(draw_list[number])
if len(participants_list) > 0:
t.add_row(["%d. draw" % (i + 1), "%d - %s" % (draw_list[number], participants_list[draw_list[number] - 1])])
else:
t.add_row(["%d. draw" % (i + 1), draw_list[number]])
if without_replacement:
draw_list.pop(number)
body = "The following results can be checked with:\n"
body += "```\n"
if without_replacement:
body += "beempy draw -d %d -p %d -b %d -t %s -h %s -s '%s' -w\n" % (draws, participants, block["id"], trx_id, hashtype, separator)
else:
body += "beempy draw -d %d -p %d -b %d -t %s -h %s -s '%s'\n" % (draws, participants, block["id"], trx_id, hashtype, separator)
body += "```\n\n"
body += "| key | value |\n"
body += "| --- | --- |\n"
body += "| block number | [%d](https://hiveblocks.com/b/%d#%s) |\n" % (block["id"], block["id"], trx_id)
body += "| trx id | [%s](https://hiveblocks.com/tx/%s) |\n" % (trx_id, trx_id)
body += "| block id | %s |\n" % block["block_id"]
body += "| previous id | %s |\n" % block["previous"]
body += "| hash type | %s |\n" % hashtype
body += "| draws | %d |\n" % draws
body += "| participants | %d |\n" % participants
i = 0
for result in results:
i += 1
if len(participants_list) > 0:
body += "| %d. draw | %d - %s |\n" % (i, result, participants_list[result - 1])
else:
body += "| %d. draw | %d |\n" % (i, result)
if markdown:
print(body)
else:
print(t)
if reply:
reply_comment.reply(body, author=account)
if __name__ == "__main__":
if getattr(sys, 'frozen', False):
os.environ['SSL_CERT_FILE'] = os.path.join(sys._MEIPASS, 'lib', 'cert.pem')
cli(sys.argv[1:])
else:
cli()