First version, adapted from my CN tipbot

This commit is contained in:
moneromooo
2015-03-08 17:23:23 +00:00
commit fa581fac00
7 changed files with 842 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
monero-python is a Python wrapper around the Monero RPC API.
It allows receiving and sending Monero using a Python API.
Licensed as BSD.
Requirements:
- Monero
- Python 2.x
- Redis
The API consists of a single class, Monero, with a number of methods.
Calling raw module functions is undocumented and subject to change.
* Initialization:
monero=Monero(daemon_host,daemon_port,wallet_host,wallet_port,redis_host,redis_port)
* Checking the current balance:
full_balance,unlocked_balance = monero.GetWalletBalance()
The full balance is the total amount (in atomic units, of which a
monero contains 1e12, or 1000000000000) in the wallet. The unlocked
balance is the portion of it that may be used now. Part of the
balance may be locked if it was just mined or received (including
as part of change to an outgoing transaction). Those locked funds
will become unlocked as new blocks are found by the network.
* Create a new payment ID for a recipient:
payment_id=monero.GetPaymentID(recipient)
recipient is a string representing a user, destination, etc. The
semantics of it are left to the API user. Numbers can be used if
they're passed as strings. CheckForDeposits and other such functions
will return that recipient when the associated payment ID is found
in a transaction.
Payment IDs are deterministic, so asking for a payment ID for a
given recipient will always yield the same payment ID.
* Get the recipient corresponding to a payment ID:
recipient=monero.GetRecipient(payment_id)
Returns the recipient associated with the given payment ID, or None
if there is none. If there is one, it is the string that was passed
to a previous call to GetPaymentID.
* Check for new deposits:
payments=monero.CheckForDeposits()
payments will be either None, in case of an error, or an array
containing payment information. This array may be empty if no new
transactions were found. Deposits are checked for all payment IDs
that were created with GetPaymentID.
Each member of the array has the following fields:
tx_hash (string): the transaction hash for that transaction
amount (long): the amount of atomic units received
confirmed (boolean): whether that transaction has enough confirmations
confirmations (int): number of blocks received on top of the block with this transaction
payment_id (string): the payment id associated with that transaction, None if none
recipient (string): the recipient corresponding to that payment id, or None
* Get full transaction history:
payments=monero.GetDepositHistory(paymentid=[])
Returns the same data format as CheckForDeposits, but for all time,
rather that just new ones. This retrieves all deposits matching the
given filters.
A payment ID of None will return history for all the payment IDs created
by GetPaymentID. [] will return all history, regardless of payment ID.
A payment ID string will return history for this particular payment ID.
A list of payment IDs will return history for all these payment IDs.
Since a list is returned, it is easy to perform extra steps with the list,
such as accumulating amounts, eg:
total = sum(tx['amount'] for tx in payments)
* Send monero to an address:
tx_hash=monero.Send(address,amount,paymentid=None,mixin=None)
Sends some monero to the address. The amount is in atomic units.
The payment ID and mixin are optional. It is recommended to use
a mixin equal to 3 or higher. The network will take a fee for this
transaction, so you will be unable to send the full balance. At
the time of writing, the network fee is 0.01 monero per kilobyte.
* Send monero to several addresses at once:
tx_hash=monero.SendMany(address,amount,paymentid=None,mixin=None)
Sends some monero to a number of addresses. The recipient parameter
is a dict containing the amounts (in atomic units) as a value of
the address in the key (ie, {'4....': 823900000000}).
Other comments from Send also apply.
View File
+48
View File
@@ -0,0 +1,48 @@
#!/bin/python
#
# oython-monero - logging
# Copyright 2014 moneromooo
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import time
def log(stype,msg):
header = "%s\t%s\t" % (time.ctime(time.time()),stype)
print "%s%s" % (header, str(msg).replace("\n","\n"+header))
def log_error(msg):
log("ERROR",msg)
def log_warn(msg):
log("WARNING",msg)
def log_info(msg):
log("INFO",msg)
def log_log(msg):
log("LOG",msg)
+63
View File
@@ -0,0 +1,63 @@
#!/bin/python
#
# python-monero
# Copyright 2015 moneromooo
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import utils
import redisdb
import payment
class Monero:
def __init__(self,daemon_host,daemon_port,wallet_host,wallet_port,redis_host,redis_port):
redisdb.connect_to_redis(redis_host,redis_port)
self.daemon_host=daemon_host
self.daemon_port=daemon_port
self.wallet_host=wallet_host
self.wallet_port=wallet_port
def GetWalletBalance(self):
return utils.RetrieveWalletBalance(self.wallet_host,self.wallet_port)
def GetPaymentID(self,recipient):
return utils.GetPaymentIDFromUserID(recipient)
def GetRecipient(self,payment_id):
return utils.GetUserIDFromPaymentID(payment_id)
def CheckForDeposits(self,confirmations=6):
return payment.CheckForDeposits(self.daemon_host,self.daemon_port,self.wallet_host,self.wallet_port,confirmations)
def GetDepositHistory(self,paymentid=[]):
return payment.GetDepositHistory(self.daemon_host,self.daemon_port,self.wallet_host,self.wallet_port,paymentid)
def Send(self,address,amount,paymentid=None,mixin=3):
return payment.Send(self.wallet_host,self.wallet_port,address,amount,paymentid,mixin)
def SendMany(self,recipients,paymentid=None,mixin=3):
return payment.SendMany(self.wallet_host,self.wallet_port,recipients,paymentid,mixin)
+237
View File
@@ -0,0 +1,237 @@
#!/bin/python
#
# python-monero - payment
# Copyright 2014,2015 moneromooo
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import redis
import time
from log import log_error, log_warn, log_info, log_log
from utils import *
from redisdb import *
def GetWalletAddress(wallet_host,wallet_port):
try:
j = SendJSONRPCCommand(wallet_host,wallet_port,"getaddress",None)
if not "result" in j:
log_error('GetWalletAddress: No result found in getaddress reply')
return None
result = j["result"]
if not "address" in result:
log_error('GetWalletAddress: No address found in getaddress reply')
return None
return result["address"]
except Exception,e:
log_error("GetWalletAddress: Error retrieving wallet address: %s" % (str(e)))
return None
def UpdateCoin(daemon_host,daemon_port,wallet_host,wallet_port,full_history=False,paymentid=None,config_confirmations=6):
txs=[]
executed_txs=[]
try:
try:
if full_history:
scan_block_height = 0
else:
scan_block_height = redis_get("scan_block_height")
scan_block_height = long(scan_block_height)
except Exception,e:
log_error('Failed to get scan_block_height: %s' % str(e))
return None
try:
j = SendHTMLCommand(daemon_host,daemon_port,"getheight")
except Exception,e:
log_error('UpdateCoin: error getting height: %s' % str(e))
return None
if not "height" in j:
log_error('UpdateCoin: error getting height: height not found in %s' % str(j))
return None
try:
height=long(j["height"])
except Exception,e:
log_error('UpdateCoin: error getting height: %s' % str(e))
return None
if paymentid != None:
if isinstance(paymentid,list):
payment_ids = paymentid
else:
payment_ids = [paymentid]
else:
full_payment_ids = redis_hgetall("paymentid")
#print 'Got full payment ids: %s' % str(full_payment_ids)
payment_ids = []
for pid in full_payment_ids:
payment_ids.append(pid)
#print 'Got payment ids: %s' % str(payment_ids)
params = {
"payment_ids": payment_ids,
"min_block_height": scan_block_height
}
j = SendJSONRPCCommand(wallet_host,wallet_port,"get_bulk_payments",params)
#print 'Got j: %s' % str(j)
if "result" in j:
result = j["result"]
cp = redis_pipeline()
if not full_history:
cp.delete('confirming_payments')
if "payments" in result:
payments = result["payments"]
new_payments = []
n_confirming = 0
new_scan_block_height = scan_block_height
for p in payments:
payment_id=p["payment_id"]
tx_hash = p["tx_hash"]
bh = p["block_height"]
ut = p["block_height"]
amount=p["amount"]
if not full_history and redis_sismember("processed_txs",tx_hash):
continue
log_log('UpdateCoin: Looking at payment %s' % str(p))
confirmations = height-1-bh
confirmations_needed = max(config_confirmations,ut-height)
if confirmations >= confirmations_needed:
log_info('Payment %s is now confirmed' % str(p))
new_payments.append(p)
if new_scan_block_height and bh > new_scan_block_height:
new_scan_block_height = bh
else:
log_info('Payment %s has %d/%d confirmations' % (str(p),confirmations,confirmations_needed))
n_confirming += 1
new_scan_block_height = None
try:
recipient = GetUserIDFromPaymentID(payment_id)
if not recipient:
raise RuntimeError('Payment ID %s not found' % payment_id)
log_info('UpdateCoin: Found payment %s to %s for %s' % (tx_hash,recipient, AmountToString(amount)))
if not full_history:
cp.hincrby('confirming_payments',recipient,amount)
txs.append({'tx_hash':tx_hash,'amount':amount,'confirmed':False,'confirmations':confirmations,'payment_id':payment_id,'recipient':recipient})
except Exception,e:
log_error('UpdateCoin: No identity found for payment id %s, tx hash %s, amount %s: %s' % (payment_id, tx_hash, amount, str(e)))
payments=new_payments
log_info('UpdateCoin: Got %d mature payments and %d confirming payments' % (len(payments),n_confirming))
if len(payments) > 0:
if not full_history and new_scan_block_height and new_scan_block_height > scan_block_height:
scan_block_height = new_scan_block_height
log_log('UpdateCoin: increasing scan_block_height to %d' % scan_block_height)
try:
pipe = redis_pipeline()
if not full_history:
pipe.set("scan_block_height", scan_block_height)
log_log('UpdateCoin: processing payments')
for p in payments:
payment_id=p["payment_id"]
tx_hash=p["tx_hash"]
amount=p["amount"]
bh = p["block_height"]
ut = p["unlock_time"]
confirmations = height-1-bh
try:
recipient = GetUserIDFromPaymentID(payment_id)
if not recipient:
raise RuntimeError('Payment ID %s not found' % payment_id)
log_info('UpdateCoin: Found payment %s to %s for %s' % (tx_hash,recipient, AmountToString(amount)))
if not full_history:
pipe.sadd("processed_txs",tx_hash)
txs.append({'tx_hash':tx_hash,'amount':amount,'confirmed':True,'confirmations':confirmations,'payment_id':payment_id,'recipient':recipient})
except Exception,e:
log_error('UpdateCoin: No identity found for payment id %s, tx hash %s, amount %s: %s' % (payment_id, tx_hash, amount, str(e)))
log_log('UpdateCoin: Executing received payments pipeline')
if not full_history:
pipe.execute()
except Exception,e:
log_error('UpdateCoin: failed to set scan_block_height: %s' % str(e))
if not full_history:
cp.execute()
executed_txs=txs
else:
log_error('UpdateCoin: No results in get_bulk_payments reply')
except Exception,e:
log_error('UpdateCoin: Failed to get bulk payments: %s' % str(e))
return executed_txs
def SendMany(wallet_host,wallet_port,recipients,paymentid=None,mixin=None):
for address in recipients:
if not IsValidAddress(address):
log_error("Invalid address: %s" % address)
return
amount = recipients[address]
if amount <= 0:
log_error("Invalid amount: %s" % str(amount))
return
if paymentid != None:
if not IsValidPaymentID(paymentid):
log_error("Invalid payment ID")
return
if mixin < 0:
log_error("Invalid mixin: %d")
return
log_info("Pay: sending %s, payment id %s, mixin %d" % (str(recipients), str(paymentid),mixin))
try:
destinations=[]
for address in recipients:
destinations.append({'address':address,'amount':recipients[address]})
params = {
'destinations':destinations,
'payment_id': paymentid,
'fee': 0,
'mixin': mixin,
'unlock_time': 0,
}
j = SendJSONRPCCommand(wallet_host,wallet_port,"transfer",params)
except Exception,e:
log_error('Withdraw: Error in transfer: %s' % str(e))
return
if not "result" in j:
log_error('Withdraw: No result in transfer reply')
return
result = j["result"]
if not "tx_hash" in result:
log_error('Withdraw: No tx_hash in transfer reply')
return
tx_hash = result["tx_hash"]
log_info('tx sent, tx_hash %s' % (str(tx_hash)))
return tx_hash
def Send(wallet_host,wallet_port,address,amount,paymentid=None,mixin=None):
return SendMany(wallet_host,wallet_port,{address: amount},paymentid,mixin)
def CheckForDeposits(daemon_host,daemon_port,wallet_host,wallet_port,confirmations):
return UpdateCoin(daemon_host,daemon_port,wallet_host,wallet_port,full_history=False,config_confirmations=confirmations)
def GetDepositHistory(daemon_host,daemon_port,wallet_host,wallet_port,paymentid=[]):
return UpdateCoin(daemon_host,daemon_port,wallet_host,wallet_port,full_history=True,paymentid=paymentid)
+153
View File
@@ -0,0 +1,153 @@
#!/bin/python
#
# python-monero
# Copyright 2014,2015 moneromooo
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import redis
from log import log_error, log_warn, log_info, log_log
redisdb = None
def connect_to_redis(host,port):
log_info('Connecting to Redis at %s:%u' % (host, port))
try:
global redisdb
redisdb = redis.Redis(host=host,port=port)
return redisdb
except Exception, e:
log_error( 'Error initializing redis: %s' % str(e))
exit()
def redis_pipeline():
return redisdb.pipeline()
def redis_exists(k):
return redisdb.exists(k)
def redis_get(k):
return redisdb.get(k)
def redis_set(k,v):
return redisdb.set(k,v)
def redis_hexists(t,k):
return redisdb.hexists(t,k)
def redis_hget(t,k):
return redisdb.hget(t,k)
def redis_hgetall(t):
return redisdb.hgetall(t)
def redis_hset(t,k,v):
return redisdb.hset(t,k,v)
def redis_hincrby(t,k,v):
return redisdb.hincrby(t,k,v)
def redis_hdel(t,k):
return redisdb.hdel(t,k)
def redis_incrby(k,v):
return redisdb.incrby(k,v)
def redis_sadd(k,v):
return redisdb.sadd(k,v)
def redis_smembers(k):
return redisdb.smembers(k)
def redis_sismember(k,v):
return redisdb.sismember(k,v)
def redis_rpush(k,v):
return redisdb.rpush(k,v)
def redis_lpop(k):
return redisdb.lpop(k)
def redis_llen(k):
return redisdb.llen(k)
def redis_lindex(k,n):
return redisdb.lindex(k,n)
def redis_lset(k,i,v):
return redisdb.lset(k,i,v)
def redis_zincrby(t,k,s):
return redisdb.zincrby(t,k,s)
def redis_zscore(t,k):
return redisdb.zscore(t,k)
def redis_zrangebylex(t,x0,x1):
return redisdb.zrangebylex(t,x0,x1)
def redis_keys(s):
return redisdb.keys(s)
def redis_delete(k):
return redisdb.delete(k)
def CompatibilityCheck():
try:
r = redis.Redis()
if not r.pipeline: raise RuntimeError('pipeline call not found')
p = r.pipeline()
if not p.exists: raise RuntimeError('exists call not found')
if not p.get: raise RuntimeError('get call not found')
if not p.set: raise RuntimeError('set call not found')
if not p.hexists: raise RuntimeError('hexists call not found')
if not p.hget: raise RuntimeError('hget call not found')
if not p.hgetall: raise RuntimeError('hgetall call not found')
if not p.hset: raise RuntimeError('hset call not found')
if not p.hincrby: raise RuntimeError('hincrby call not found')
if not p.hdel: raise RuntimeError('hdel call not found')
if not p.incrby: raise RuntimeError('incrby call not found')
if not p.sadd: raise RuntimeError('sadd call not found')
if not p.smembers: raise RuntimeError('smembers call not found')
if not p.sismember: raise RuntimeError('sismember call not found')
if not p.rpush: raise RuntimeError('rpush call not found')
if not p.lpop: raise RuntimeError('lpop call not found')
if not p.llen: raise RuntimeError('llen call not found')
if not p.lindex: raise RuntimeError('lindex call not found')
if not p.lset: raise RuntimeError('lset call not found')
if not p.zincrby: raise RuntimeError('zincrby call not found')
if not p.zscore: raise RuntimeError('zscore call not found')
if not p.zrangebylex: raise RuntimeError('zrangebylex call not found')
if not p.keys: raise RuntimeError('keys call not found')
if not p.execute: raise RuntimeError('execute call not found')
if not p.delete: raise RuntimeError('delete call not found')
except Exception,e:
log_error('Error checking redis compatibility: %s' % str(e))
exit(1)
CompatibilityCheck()
+243
View File
@@ -0,0 +1,243 @@
#!/bin/python
#
# python-monero - utility functions
# Copyright 2014,2015 moneromooo
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import redis
import hashlib
import json
import httplib
import time
import string
from decimal import *
from log import log_error, log_warn, log_info, log_log
from redisdb import *
address_length = [95, 95] # min/max size of addresses
address_prefix = ['4', '9'] # allowed prefixes of addresses
cached_wallet_balance=None
cached_wallet_unlocked_balance=None
cached_wallet_balance_timestamp=None
def GetPaymentIDFromUserID(user_id):
salt="nveuweaiirv-"
p = hashlib.sha256(salt+user_id).hexdigest()
try:
redis_hset("paymentid",p,link.identity())
except Exception,e:
log_error('GetPaymentIDFromUserID: failed to set payment ID for %s to redis: %s' % (user_id,str(e)))
return p
def GetUserIDFromPaymentID(paymentid):
if not redis_hexists("paymentid",paymentid):
log_log('PaymentID %s not found' % paymentid)
return None
identity = redis_hget("paymentid",paymentid)
log_log('PaymentID %s => %s' % (paymentid, str(identity)))
return identity
def IsValidAddress(address):
if len(address) < address_length[0] or len(address) > address_length[1]:
return False
for prefix in address_prefix:
if address.startswith(prefix):
return True
return False
def IsValidPaymentID(payment_id):
if len(payment_id)!=64:
return False
for char in payment_id:
if char not in string.hexdigits:
return False
return True
def SendJSONRPCCommand(host,port,method,params):
try:
http = httplib.HTTPConnection(host,port,timeout=20)
except Exception,e:
log_error('SendJSONRPCCommand: Error connecting to %s:%u: %s' % (host, port, str(e)))
raise
d = dict(id="0",jsonrpc="2.0",method=method,params=params)
try:
j = json.dumps(d).encode()
except Exception,e:
log_error('SendJSONRPCCommand: Failed to encode JSON: %s' % str(e))
http.close()
raise
log_log('SendJSONRPCCommand: Sending json as body: %s' % j)
headers = None
try:
http.request("POST","/json_rpc",body=j)
except Exception,e:
log_error('SendJSONRPCCommand: Failed to post request: %s' % str(e))
http.close()
raise
response = http.getresponse()
if response.status != 200:
log_error('SendJSONRPCCommand: Error, received reply status %s' % str(response.status))
http.close()
raise RuntimeError("Error "+response.status)
s = response.read()
log_log('SendJSONRPCCommand: Received reply status %s: %s' % (response.status, str(s).replace('\r\n',' ').replace('\n',' ')))
try:
j = json.loads(s)
except Exception,e:
log_error('SendJSONRPCCommand: Failed to decode JSON: %s' % str(e))
http.close()
raise
http.close()
return j
def SendHTMLCommand(host,port,method):
try:
http = httplib.HTTPConnection(host,port,timeout=20)
except Exception,e:
log_error('SendHTMLCommand: Error connecting to %s:%u: %s' % (host, port, str(e)))
raise
headers = None
try:
http.request("POST","/"+method)
except Exception,e:
log_error('SendHTMLCommand: Failed to post request: %s' % str(e))
http.close()
raise
response = http.getresponse()
if response.status != 200:
log_error('SendHTMLCommand: Error, received reply status %s' % str(response.status))
http.close()
raise RuntimeError("Error "+response.status)
s = response.read()
log_log('SendHTMLCommand: Received reply status %s: %s' % (response.status,s.replace('\r\n',' ').replace('\n',' ')))
try:
j = json.loads(s)
except Exception,e:
log_error('SendHTMLCommand: Failed to decode JSON: %s' % str(e))
http.close()
raise
http.close()
return j
def RetrieveWalletBalance(wallet_host,wallet_port,force_refresh=False):
global cached_wallet_balance, cached_wallet_unlocked_balance, cached_wallet_balance_timestamp
if not force_refresh and cached_wallet_balance_timestamp and time.time()-cached_wallet_balance_timestamp < 35:
return cached_wallet_balance, cached_wallet_unlocked_balance
j = SendJSONRPCCommand(wallet_host,wallet_port,"getbalance",None)
if not "result" in j:
log_error('RetrieveWalletBalance: result not found in reply')
raise RuntimeError("")
return
result = j["result"]
if not "balance" in result:
log_error('RetrieveWalletBalance: balance not found in result')
raise RuntimeError("")
return
if not "unlocked_balance" in result:
log_error('RetrieveWalletBalance: unlocked_balance not found in result')
raise RuntimeError("")
return
balance = result["balance"]
unlocked_balance = result["unlocked_balance"]
log_log('RetrieveWalletBalance: balance: %s' % str(balance))
log_log('RetrieveWalletBalance: unlocked_balance: %s' % str(unlocked_balance))
pending = long(balance)-long(unlocked_balance)
if pending < 0:
log_error('RetrieveWalletBalance: Negative pending balance! balance %s, unlocked %s' % (str(balance),str(unlocked_balance)))
raise RuntimeError("")
return
cached_wallet_balance_timestamp=time.time()
cached_wallet_balance=balance
cached_wallet_unlocked_balance=unlocked_balance
return balance, unlocked_balance
# Code taken from the Python documentation
def moneyfmt(value, places=2, curr='', sep=',', dp='.',
pos='', neg='-', trailneg=''):
"""Convert Decimal to a money formatted string.
places: required number of places after the decimal point
curr: optional currency symbol before the sign (may be blank)
sep: optional grouping separator (comma, period, space, or blank)
dp: decimal point indicator (comma or period)
only specify as blank when places is zero
pos: optional sign for positive numbers: '+', space or blank
neg: optional sign for negative numbers: '-', '(', space or blank
trailneg:optional trailing minus indicator: '-', ')', space or blank
>>> d = Decimal('-1234567.8901')
>>> moneyfmt(d, curr='$')
'-$1,234,567.89'
>>> moneyfmt(d, places=0, sep='.', dp='', neg='', trailneg='-')
'1.234.568-'
>>> moneyfmt(d, curr='$', neg='(', trailneg=')')
'($1,234,567.89)'
>>> moneyfmt(Decimal(123456789), sep=' ')
'123 456 789.00'
>>> moneyfmt(Decimal('-0.02'), neg='<', trailneg='>')
'<0.02>'
"""
q = Decimal(10) ** -places # 2 places --> '0.01'
sign, digits, exp = value.quantize(q).as_tuple()
result = []
digits = map(str, digits)
build, next = result.append, digits.pop
if sign:
build(trailneg)
for i in range(places):
build(next() if digits else '0')
build(dp)
if not digits:
build('0')
i = 0
while digits:
build(next())
i += 1
if i == 3 and digits:
i = 0
build(sep)
build(curr)
build(neg if sign else pos)
s = ''.join(reversed(result))
if dp in s:
s=s.strip('0').rstrip(dp)
if s=="" or s[0]==dp:
s="0"+s
return s
def AmountToString(amount):
if amount == None:
amount = 0
lamount=long(amount)
samount = moneyfmt(Decimal(lamount)/Decimal(1e12),places=12) + " monero"
return samount