2014-01-07 10:22:21 -08:00
|
|
|
#!/usr/bin/env python
|
2014-10-02 18:14:42 -07:00
|
|
|
"""
|
|
|
|
.. module:: CryptoWalletRPC
|
2015-11-01 18:03:11 -08:00
|
|
|
:synopsis: Module capable of operating bitcoind-style RPC. Provided as a service.
|
2014-10-02 18:14:42 -07:00
|
|
|
|
|
|
|
.. moduleauthor:: Dave Pedu <dave@davepedu.com>
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2017-01-01 14:59:01 -08:00
|
|
|
from pyircbot.modulebase import ModuleBase
|
2014-01-07 10:22:21 -08:00
|
|
|
from bitcoinrpc.authproxy import AuthServiceProxy
|
2018-02-10 15:58:19 -08:00
|
|
|
import re
|
2014-01-07 10:22:21 -08:00
|
|
|
from threading import Thread
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2014-01-07 10:22:21 -08:00
|
|
|
|
|
|
|
class CryptoWalletRPC(ModuleBase):
|
2015-11-01 18:03:11 -08:00
|
|
|
def __init__(self, bot, moduleName):
|
2017-01-01 14:59:01 -08:00
|
|
|
ModuleBase.__init__(self, bot, moduleName)
|
|
|
|
self.services = ["bitcoinrpc"]
|
|
|
|
self.rpcservices = {}
|
2015-11-01 18:03:11 -08:00
|
|
|
self.loadrpcservices()
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def loadrpcservices(self):
|
|
|
|
# Create a dict of abbreviation=>BitcoinRPC objcet relation
|
|
|
|
self.log.info("CryptoWalletRPC: loadrpcservices: connecting to RPCs")
|
2018-02-10 15:58:19 -08:00
|
|
|
for abbr, coin in self.config["types"].items():
|
|
|
|
self.rpcservices[abbr.lower()] = BitcoinRPC(self,
|
|
|
|
abbr.lower(),
|
|
|
|
coin["name"],
|
|
|
|
coin["host"],
|
|
|
|
coin["port"],
|
|
|
|
coin["username"],
|
|
|
|
coin["password"],
|
|
|
|
coin["precision"],
|
|
|
|
coin["reserve"],
|
|
|
|
re.compile(coin["addrfmt"]))
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getRpc(self, currencyAbbr):
|
|
|
|
# Return the rpc for the currency requested
|
|
|
|
# self.getRpc("LTC") -> returns a litecoin rpc instance
|
|
|
|
currencyAbbr = currencyAbbr.lower()
|
|
|
|
if currencyAbbr in self.rpcservices:
|
|
|
|
return self.rpcservices[currencyAbbr]
|
|
|
|
return None
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getSupported(self):
|
|
|
|
# return a list of (appreviatons of) supported currencies
|
|
|
|
return list(self.rpcservices.keys())
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def isSupported(self, abbr):
|
|
|
|
# true/false if currency is supported
|
|
|
|
supported = self.getSupported()
|
|
|
|
return abbr.lower() in supported
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2018-02-10 15:58:19 -08:00
|
|
|
# def validate_addr(self, coin_abbr, address):
|
|
|
|
# client = self.getRpc(coin_abbr.lower())
|
|
|
|
# if not client or not client.validate_addr(:
|
|
|
|
# return False
|
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getInfo(self, abbr):
|
|
|
|
# return the coin's info from config
|
|
|
|
if self.isSupported(abbr):
|
|
|
|
return self.config["types"][abbr.upper()]
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2014-01-07 10:22:21 -08:00
|
|
|
|
2018-02-10 15:58:19 -08:00
|
|
|
class BitcoinRPC(object):
|
|
|
|
def __init__(self, parent, name, fullname, host, port, username, password, precision, reserve, addr_re):
|
2015-11-01 18:03:11 -08:00
|
|
|
# Store info and connect
|
|
|
|
self.master = parent
|
|
|
|
self.name = name
|
|
|
|
self.host = host
|
|
|
|
self.port = port
|
|
|
|
self.username = username
|
|
|
|
self.password = password
|
|
|
|
self.precision = precision
|
|
|
|
self.reserve = reserve
|
2018-02-10 15:58:19 -08:00
|
|
|
self.addr_re = addr_re
|
2015-11-01 18:03:11 -08:00
|
|
|
self.log = self.master.log
|
2018-02-10 15:58:19 -08:00
|
|
|
self.con = None # AuthServiceProxy (bitcoin json rpc client) stored here
|
|
|
|
Thread(target=self.ping).start() # Initiate rpc connection
|
|
|
|
|
|
|
|
def validate_addr(self, addr):
|
|
|
|
"""
|
|
|
|
Validate an address string. Returns true if the `addr` provided is a valid address string
|
|
|
|
:param addr: address to validate
|
|
|
|
:type addr: str
|
|
|
|
:return: bool
|
|
|
|
"""
|
|
|
|
return True if type(addr) is str and self.addr_re.match(addr) else False
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getBal(self, acct):
|
2017-01-01 14:59:01 -08:00
|
|
|
# get a balance of an address or an account
|
2015-11-01 18:03:11 -08:00
|
|
|
return self.getAcctBal(acct)
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getAcctAddr(self, acct):
|
2017-01-01 14:59:01 -08:00
|
|
|
# returns the address for an account. creates if necessary
|
2015-11-01 18:03:11 -08:00
|
|
|
self.ping()
|
|
|
|
addrs = self.con.getaddressesbyaccount(acct)
|
2017-01-01 14:59:01 -08:00
|
|
|
if len(addrs) == 0:
|
2015-11-01 18:03:11 -08:00
|
|
|
return self.con.getnewaddress(acct)
|
|
|
|
return addrs[0]
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def getAcctBal(self, acct):
|
|
|
|
# returns an account's balance
|
|
|
|
self.ping()
|
|
|
|
return float(self.con.getbalance(acct))
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def canMove(self, fromAcct, toAcct, amount):
|
2017-01-01 14:59:01 -08:00
|
|
|
# true or false if fromAcct can afford to give toAcct an amount of coins
|
2015-11-01 18:03:11 -08:00
|
|
|
balfrom = self.getAcctBal(fromAcct)
|
2018-02-10 15:58:19 -08:00
|
|
|
return (balfrom - self.reserve) >= amount
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def move(self, fromAcct, toAcct, amount):
|
2017-01-01 14:59:01 -08:00
|
|
|
# move coins from one account to another
|
2015-11-01 18:03:11 -08:00
|
|
|
self.ping()
|
|
|
|
if self.canMove(fromAcct, toAcct, amount):
|
|
|
|
return self.con.move(fromAcct, toAcct, amount)
|
|
|
|
return False
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def send(self, fromAcct, toAddr, amount):
|
2017-01-01 14:59:01 -08:00
|
|
|
# send coins to an external addr
|
2015-11-01 18:03:11 -08:00
|
|
|
self.ping()
|
|
|
|
if self.canMove(fromAcct, toAddr, amount):
|
|
|
|
return self.con.sendfrom(fromAcct, toAddr, amount)
|
|
|
|
return False
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def checkPrecision(self, amount):
|
|
|
|
return amount == round(amount, self.precision)
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def ping(self):
|
|
|
|
# internal. test connection and connect if necessary
|
|
|
|
try:
|
|
|
|
self.con.getinfo()
|
|
|
|
except:
|
|
|
|
self.connect()
|
2017-01-01 14:59:01 -08:00
|
|
|
|
2015-11-01 18:03:11 -08:00
|
|
|
def connect(self):
|
|
|
|
# internal. connect to the service
|
2017-01-01 14:59:01 -08:00
|
|
|
self.log.info("CryptoWalletRPC: %s: Connecting to %s:%s" % (self.name, self.host, self.port))
|
2015-11-01 18:03:11 -08:00
|
|
|
try:
|
|
|
|
self.con = AuthServiceProxy("http://%s:%s@%s:%s" % (self.username, self.password, self.host, self.port))
|
|
|
|
except Exception as e:
|
2017-01-01 14:59:01 -08:00
|
|
|
self.log.error("CryptoWalletRPC: %s: Could not connect to %s:%s: %s" %
|
|
|
|
(self.name, self.host, self.port, str(e)))
|
2015-11-01 18:03:11 -08:00
|
|
|
return
|
2014-01-07 10:22:21 -08:00
|
|
|
|
2017-01-01 14:59:01 -08:00
|
|
|
self.log.info("CryptoWalletRPC: %s: Connected to %s:%s" % (self.name, self.host, self.port))
|