pyircbot/pyircbot/modules/CryptoWallet.py

292 lines
14 KiB
Python
Raw Normal View History

2014-01-07 10:22:21 -08:00
#!/usr/bin/env python
2014-10-02 18:14:42 -07:00
"""
.. module:: Error
2015-11-01 18:03:11 -08:00
:synopsis: Module to provide a multi-type cryptocurrency wallet
2014-10-02 18:14:42 -07:00
.. moduleauthor:: Dave Pedu <dave@davepedu.com>
"""
2018-02-10 15:58:19 -08:00
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
from decimal import Decimal
2014-01-07 10:22:21 -08:00
import time
import hashlib
2017-01-01 14:59:01 -08:00
2014-01-07 10:22:21 -08:00
class CryptoWallet(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)
2015-11-01 18:03:11 -08:00
def getMods(self):
2015-11-29 19:12:45 -08:00
return (self.bot.getBestModuleForService("attributes"), self.bot.getBestModuleForService("bitcoinrpc"))
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
@info("setaddr <currency> <address> set withdraw address", cmds=["setaddr"])
@command("setaddr", require_args=2, allow_private=True)
def handle_setaddr(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
2015-11-01 18:03:11 -08:00
return
2018-02-10 15:58:19 -08:00
attr, rpc = self.getMods()
2015-11-01 18:03:11 -08:00
# Check if currency is known
if not rpc.isSupported(cmd.args[0]):
supportedStr = ', '.join(rpc.getSupported())
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".setaddr: '{}' is not a supported currency. Supported currencies are: {}"
.format(cmd.args[0], supportedStr))
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
if len(cmd.args[1]) < 16 or len(cmd.args[1]) > 42:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".setaddr: '{}' appears to be an invalid address.".format(cmd.args[1]))
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# Just make sure they have a wallet
2018-02-10 15:58:19 -08:00
self.checkUserHasWallet(msg.prefix.nick, cmd.args[0])
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# Set their address
2018-02-10 15:58:19 -08:00
attr.setKey(msg.prefix.nick, "cryptowallet-{}-address".format(cmd.args[0].lower()), cmd.args[1])
self.bot.act_PRIVMSG(msg.args[0], ".setaddr: Your address has been saved as: {}. Please verify that this is "
"correct or your coins could be lost.".format(cmd.args[1]))
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
@info("getbal <currency> retrieve your balance ", cmds=["getbal"])
@command("getbal", require_args=1, allow_private=True)
def handle_getbal(self, msg, cmd):
2015-11-01 18:03:11 -08:00
usage = ".getbal <currency>"
2018-02-10 15:58:19 -08:00
if not self.check_login(msg.prefix, msg.args[0]):
return
2017-01-01 14:59:01 -08:00
attr, rpc = self.getMods()
2015-11-01 18:03:11 -08:00
# Check for args
2018-02-10 15:58:19 -08:00
if len(cmd.args) != 1:
self.bot.act_PRIVMSG(msg.args[0], ".getbal: usage: {}".format(usage))
2015-11-01 18:03:11 -08:00
return
# Check if currency is known
if not rpc.isSupported(cmd.args[0]):
supportedStr = ', '.join(rpc.getSupported())
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0],
".getbal: '{}' is not a supported currency. Supported currencies are: {}"
.format(cmd.args[0], supportedStr))
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# Just make sure they have a wallet
2018-02-10 15:58:19 -08:00
self.checkUserHasWallet(msg.prefix.nick, cmd.args[0])
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# fetch RPC and tell them the balance
2018-02-10 15:58:19 -08:00
walletname = attr.getKey(msg.prefix.nick, "cryptowallet-account-{}".format(cmd.args[0].lower()))
2015-11-01 18:03:11 -08:00
amount = 0.0
if walletname:
client = rpc.getRpc(cmd.args[0].lower())
amount = client.getBal(walletname)
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0],
"{}: your balance is: {} {}".format(msg.prefix.nick, amount, cmd.args[0].upper()))
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
@info("withdraw <currenyc> <amount> send coins to your withdraw address", cmds=["withdraw"])
@command("withdraw", require_args=2, allow_private=True)
def handle_withdraw(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
2015-11-01 18:03:11 -08:00
return
2018-02-10 15:58:19 -08:00
attr, rpc = self.getMods()
2015-11-01 18:03:11 -08:00
# Check if currency is known
if not rpc.isSupported(cmd.args[0]):
supportedStr = ', '.join(rpc.getSupported())
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".getbal: '{}' is not a supported currency. Supported currencies are: {}"
.format(cmd.args[0], supportedStr))
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# Just make sure they have a wallet
2018-02-10 15:58:19 -08:00
self.checkUserHasWallet(msg.prefix.nick, cmd.args[0])
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# check that they have a withdraw addr
2018-02-10 15:58:19 -08:00
withdrawaddr = attr.getKey(msg.prefix.nick, "cryptowallet-{}-address".format(cmd.args[0].lower()))
2017-01-01 14:59:01 -08:00
if withdrawaddr is None:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".withdraw: You need to set a withdraw address before withdrawing. "
"Try .setaddr")
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# fetch RPC and check balance
2018-02-10 15:58:19 -08:00
walletname = attr.getKey(msg.prefix.nick, "cryptowallet-account-{}".format(cmd.args[0].lower()))
2015-11-01 18:03:11 -08:00
balance = 0.0
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
client = rpc.getRpc(cmd.args[0].lower())
balance = client.getBal(walletname)
2018-02-10 15:58:19 -08:00
withdrawamount = Decimal(cmd.args[1])
2017-01-01 14:59:01 -08:00
if balance < withdrawamount or withdrawamount < 0:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".withdraw: You don't have enough {} to withdraw {}"
.format(cmd.args[0].upper(), withdrawamount))
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
if not client.reserve == 0 and balance - client.reserve < withdrawamount:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".withdraw: Withdrawing that much would put you below the reserve "
"({} {}).".format(client.reserve, cmd.args[0].upper()))
self.bot.act_PRIVMSG(msg.args[0], ".withdraw: The reserve is to cover network transaction fees. To recover "
"it you must close your account. (Talk to an admin)")
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# Check if the precision is wrong
if not client.checkPrecision(withdrawamount):
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".withdraw: {} has maximum {} decimal places"
.format(cmd.args[0].upper(), client.precision))
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# Create a transaction
txn = client.send(walletname, withdrawaddr, withdrawamount)
if txn:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{}: .withdraw: {} {} sent to {}. "
.format(msg.prefix.nick, withdrawamount, client.name, withdrawaddr))
self.bot.act_PRIVMSG(msg.prefix.nick, "Withdrawal: (You)->{}: Transaction ID: {}"
.format(withdrawaddr, txn))
2015-11-29 19:12:45 -08:00
else:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{}: .withdraw: Transaction create failed. Maybe the transaction was too "
"large for the network? Try a smaller increment.".format(msg.prefix.nick))
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
@info("send <currency> <amount> <nick_or_address> send coins elsewhere", cmds=["send"])
@command("send", require_args=3, allow_private=True)
def handle_send(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
2015-11-29 19:12:45 -08:00
return
2018-02-10 15:58:19 -08:00
attr, rpc = self.getMods()
2015-11-29 19:12:45 -08:00
# Check if currency is known
2018-02-10 15:58:19 -08:00
curr_name = cmd.args[0].lower()
if not rpc.isSupported(curr_name):
2015-11-29 19:12:45 -08:00
supportedStr = ', '.join(rpc.getSupported())
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".getbal: '{}' is not a supported currency. Supported currencies are: {}"
.format(curr_name, supportedStr))
2015-11-29 19:12:45 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-29 19:12:45 -08:00
# Just make sure they have a wallet
2018-02-10 15:58:19 -08:00
self.checkUserHasWallet(msg.prefix.nick, curr_name)
2017-01-01 14:59:01 -08:00
2015-11-29 19:12:45 -08:00
# fetch RPC and check balance
2018-02-10 15:58:19 -08:00
walletname = attr.getKey(msg.prefix.nick, "cryptowallet-account-{}".format(curr_name))
2015-11-29 19:12:45 -08:00
balance = 0.0
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
client = rpc.getRpc(curr_name.lower())
2015-11-29 19:12:45 -08:00
balance = client.getBal(walletname)
2018-02-10 15:58:19 -08:00
withdrawamount = Decimal(cmd.args[1])
tx_dest = cmd.args[2]
2017-01-01 14:59:01 -08:00
if balance < withdrawamount or withdrawamount < 0:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{}: .send: You don't have enough {} to send {}"
.format(msg.prefix.nick, curr_name.upper(), withdrawamount))
2015-11-29 19:12:45 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-29 19:12:45 -08:00
# Check if the precision is wrong
if not client.checkPrecision(withdrawamount):
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".send: {} has maximum {} decimal places"
.format(curr_name.upper(), client.precision))
2015-11-29 19:12:45 -08:00
return
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
# Check if the tx_dest is a valid address for the coin
if client.validate_addr(tx_dest):
2015-11-29 19:12:45 -08:00
# Check if we can cover network fees
if not client.reserve == 0 and balance - client.reserve < withdrawamount:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".send: Sending that much would put you below the reserve ({} {})."
.format(client.reserve, curr_name.upper()))
self.bot.act_PRIVMSG(msg.args[0], ".send: The reserve is to cover network transaction fees. To recover it"
" you must close your account. (Talk to my owner)")
2015-11-29 19:12:45 -08:00
return
# Create a transaction
2018-02-10 15:58:19 -08:00
txn = client.send(walletname, tx_dest, withdrawamount)
2015-11-29 19:12:45 -08:00
if txn:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{}: .send: {} {} sent to {}. "
.format(msg.prefix.nick, withdrawamount, client.name, tx_dest))
self.bot.act_PRIVMSG(msg.prefix.nick, "Send: (You)->{}: Transaction ID: {}".format(tx_dest, txn))
2015-11-29 19:12:45 -08:00
else:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{}: .send: Transaction create failed. Maybe the address is invalid "
"or transaction too large for the network?".format(msg.prefix.nick))
2015-11-01 18:03:11 -08:00
else:
2015-11-29 19:12:45 -08:00
# Move between local wallets
# Check if dest user has a password set
2018-02-10 15:58:19 -08:00
destUserPassword = attr.getKey(tx_dest, "password")
2017-01-01 14:59:01 -08:00
if destUserPassword is None:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{} .send: {} doesn't have a password set."
.format(msg.prefix.nick, tx_dest))
2015-11-29 19:12:45 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-29 19:12:45 -08:00
# Since the user has a password set, check that they have a wallet and create if not
2018-02-10 15:58:19 -08:00
self.checkUserHasWallet(tx_dest, curr_name)
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
srcWalletName = attr.getKey(msg.prefix.nick, "cryptowallet-account-{}".format(curr_name))
destWalletName = attr.getKey(tx_dest, "cryptowallet-account-{}".format(curr_name))
2017-01-01 14:59:01 -08:00
2015-11-29 19:12:45 -08:00
assert srcWalletName is not None
assert destWalletName is not None
2018-02-10 15:58:19 -08:00
if srcWalletName == destWalletName:
self.bot.act_PRIVMSG(msg.args[0], "{}: you can't send to yourself!".format(msg.prefix.nick))
2015-11-29 19:12:45 -08:00
return
print(srcWalletName)
print(destWalletName)
if client.canMove(srcWalletName, destWalletName, withdrawamount):
if client.move(srcWalletName, destWalletName, withdrawamount):
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{} .send: {} {} sent to {}. "
.format(msg.prefix.nick, withdrawamount, client.name, tx_dest))
2017-01-01 14:59:01 -08:00
else:
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], "{}: uh-oh, something went wrong doing that."
.format(msg.prefix.nick))
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
@info("getaddr <currency> get deposit address", cmds=["getaddr"])
@command("getaddr", require_args=1, allow_private=True)
def handle_getaddr(self, msg, cmd):
if not self.check_login(msg.prefix, msg.args[0]):
2015-11-01 18:03:11 -08:00
return
2018-02-10 15:58:19 -08:00
attr, rpc = self.getMods()
2015-11-01 18:03:11 -08:00
# Check if currency is known
if not rpc.isSupported(cmd.args[0]):
supportedStr = ', '.join(rpc.getSupported())
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".getaddr: '{}' is not a supported currency. Supported currencies are: {}"
.format(cmd.args[0], supportedStr))
2015-11-01 18:03:11 -08:00
return
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
# Just make sure they have a wallet
2018-02-10 15:58:19 -08:00
self.checkUserHasWallet(msg.prefix.nick, cmd.args[0])
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
walletaddr = attr.getKey(msg.prefix.nick, "cryptowallet-depoaddr-{}".format(cmd.args[0].lower()))
self.bot.act_PRIVMSG(msg.args[0], "{}: your {} deposit address is: {}"
.format(msg.prefix.nick, cmd.args[0].upper(), walletaddr))
2017-01-01 14:59:01 -08:00
2018-02-10 15:58:19 -08:00
@info("curinfo list supported coins", cmds=["curinfo"])
@command("curinfo", allow_private=True)
def handle_curinfo(self, msg, cmd):
2017-01-01 14:59:01 -08:00
attr, rpc = self.getMods()
2018-02-10 15:58:19 -08:00
if not cmd.args:
self.bot.act_PRIVMSG(msg.args[0],
".curinfo: supported currencies: {}. Use '.curinfo BTC' to see details. "
.format(', '.join([x.upper() for x in rpc.getSupported()])))
2015-11-01 18:03:11 -08:00
else:
if not rpc.isSupported(cmd.args[0]):
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0],
".curinfo: '{}' is not a supported currency. Supported currencies are: {}"
.format(cmd.args[0], ', '.join([x.upper() for x in rpc.getSupported()])))
2015-11-01 18:03:11 -08:00
return
else:
info = rpc.getInfo(cmd.args[0])
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(msg.args[0], ".curinfo: {} - {}. More info: {}"
.format(cmd.args[0], info["name"], info["link"]))
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
def checkUserHasWallet(self, username, currency):
# Ensure the user has a wallet in the client
2017-01-01 14:59:01 -08:00
attr, rpc = self.getMods()
2015-11-01 18:03:11 -08:00
currency = currency.lower()
2015-11-29 19:12:45 -08:00
username = username.lower()
2018-02-10 15:58:19 -08:00
if attr.getKey(username, "cryptowallet-account-{}".format(currency)) is None:
2015-11-01 18:03:11 -08:00
randName = self.md5(str(time.time()))[0:16]
2018-02-10 15:58:19 -08:00
attr.setKey(username, "cryptowallet-account-{}".format(currency), randName)
2015-11-01 18:03:11 -08:00
# Generate a deposit addr to nudge the wallet
wallet = rpc.getRpc(currency.lower())
address = wallet.getAcctAddr(randName)
2018-02-10 15:58:19 -08:00
attr.setKey(username, "cryptowallet-depoaddr-{}".format(currency), address)
elif attr.getKey(username, "cryptowallet-depoaddr-{}".format(currency)) is None:
walletName = attr.getKey(username, "cryptowallet-account-{}".format(currency))
2015-11-01 18:03:11 -08:00
wallet = rpc.getRpc(currency.lower())
address = wallet.getAcctAddr(walletName)
2018-02-10 15:58:19 -08:00
attr.setKey(username, "cryptowallet-depoaddr-{}".format(currency), address)
2017-01-01 14:59:01 -08:00
2015-11-29 19:12:45 -08:00
def check_login(self, prefix, replyTo):
login = self.bot.getBestModuleForService("login")
if not login.check(prefix.nick, prefix.hostname):
2018-02-10 15:58:19 -08:00
self.bot.act_PRIVMSG(replyTo, "{}: Please .login to use this command.".format(prefix.nick))
2015-11-29 19:12:45 -08:00
return False
return True
2017-01-01 14:59:01 -08:00
2015-11-01 18:03:11 -08:00
def md5(self, data):
m = hashlib.md5()
m.update(data.encode("ascii"))
return m.hexdigest()