mirror of
https://github.com/jwinterm/python-monero.git
synced 2026-10-17 05:51:43 +02:00
First version, adapted from my CN tipbot
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
Reference in New Issue
Block a user