Example usage of json-rpc in Python for Monero
Monero is a secure, private, untraceable cryptocurrency. For more information or questions, please go to getmonero.org and r/Monero, respectively.
The two main components of monero are simplewallet
and bitmonerod
. The first one
is the wallet, as the name suggest. The second one is monero deamon, which is responsbile
for interacting with monero blockchain.
Most important functions of Monero's simplewallet
and
bitmonreod
can be executed by means of JavaScript Object Notation Remote Procedure Calls (json-rpc).
Using these procedures, other applications can be developed
on top of the simplewallet
and bitmonerod
. For examples, a GUI wallet,
an web applications allowing for accessing wallet balance
online, and block explorer.
Since there seem to be no tutorials and/or examples of how
to use json-rpc to interact with both bitmonerod
and simplewallet
,
the following examples in Python were created. Hopefully, they will allow others to start developing some python
programs on top of Monero.
simplewallet
The examples demonstrate how to call the most popular procedures
that simplewallet
exposes for other applications to use, such as:
- getbalance
- query_key
- get_payments
- getaddress
- incoming_transfers
- transfer
The basic documentaion of the procedures can be found here.
Prerequsits
Before executing this code make sure that simplewallet
is
running and listening for the incoming rpc calls. For example, you can run the simplewallet
in rpc mode as follows:
/opt/bitmonero/simplewallet --wallet-file ~/wallet.bin --password <wallet_password> --rpc-bind-port 18082
The code was written, tested and executed on Ubuntu 15.10 with Python 3.4.3 and requires the Requests package.
Basic example 1: get wallet balance
import requests
import json
def main():
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# simplewallet' procedure/method to call
rpc_input = {
"method": "getbalance"
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# amounts in cryptonote are encoded in a way which is convenient
# for a computer, not a user. Thus, its better need to recode them
# to something user friendly, before displaying them.
#
# For examples:
# 4760000000000 is 4.76
# 80000000000 is 0.08
#
# In example 3 "Basic example 3: get incoming transfers" it is
# shown how to convert cryptonote values to user friendly format.
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Generated output:
{
"jsonrpc": "2.0",
"id": "0",
"result": {
"unlocked_balance": 4760000000000,
"balance": 4760000000000
}
}
Basic example 2: get a payment information having payment id
import requests
import json
def main():
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# an example of a payment id
payment_id = "426870cb29c598e191184fa87003ca562d9e25f761ee9e520a888aec95195912"
# simplewallet' procedure/method to call
rpc_input = {
"method": "get_payments",
"params": {"payment_id": payment_id}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Generated output:
{
"result": {
"payments": [
{
"tx_hash": "66040ad29f0d780b4d47641a67f410c28cce575b5324c43b784bb376f4e30577",
"amount": 4800000000000,
"block_height": 795523,
"payment_id": "426870cb29c598e191184fa87003ca562d9e25f761ee9e520a888aec95195912",
"unlock_time": 0
}
]
},
"jsonrpc": "2.0",
"id": "0"
}
Basic example 3: get incoming transfers
import requests
import json
def main():
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# simplewallet' procedure/method to call
rpc_input = {
"method": "incoming_transfers",
"params": {"transfer_type": "all"}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# make json dict with response
response_json = response.json()
# amounts in cryptonote are encoded in a way which is convenient
# for a computer, not a user. Thus, its better need to recode them
# to something user friendly, before displaying them.
#
# For examples:
# 4760000000000 is 4.76
# 80000000000 is 0.08
#
if "result" in response_json:
if "transfers" in response_json["result"]:
for transfer in response_json["result"]["transfers"]:
transfer["amount"] = float(get_money(str(transfer["amount"])))
# pretty print json output
print(json.dumps(response_json, indent=4))
def get_money(amount):
"""decode cryptonote amount format to user friendly format. Hope its correct.
Based on C++ code:
https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L751
"""
CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12
s = amount
if len(s) < CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1:
# add some trailing zeros, if needed, to have constant width
s = '0' * (CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1 - len(s)) + s
idx = len(s) - CRYPTONOTE_DISPLAY_DECIMAL_POINT
s = s[0:idx] + "." + s[idx:]
return s
if __name__ == "__main__":
main()
Generated output:
{
"jsonrpc": "2.0",
"result": {
"transfers": [
{
"tx_hash": "<66040ad29f0d780b4d47641a67f410c28cce575b5324c43b784bb376f4e30577>",
"tx_size": 521,
"spent": true,
"global_index": 346865,
"amount": 0.8
},
{
"tx_hash": "<66040ad29f0d780b4d47641a67f410c28cce575b5324c43b784bb376f4e30577>",
"tx_size": 521,
"spent": true,
"global_index": 177947,
"amount": 4.0
},
{
"tx_hash": "<79e7eb67b7022a21505fa034388b5e3b29e1ce639d6dec37347fefa612117ce9>",
"tx_size": 562,
"spent": false,
"global_index": 165782,
"amount": 0.08
},
{
"tx_hash": "<79e7eb67b7022a21505fa034388b5e3b29e1ce639d6dec37347fefa612117ce9>",
"tx_size": 562,
"spent": false,
"global_index": 300597,
"amount": 0.9
},
{
"tx_hash": "<79e7eb67b7022a21505fa034388b5e3b29e1ce639d6dec37347fefa612117ce9>",
"tx_size": 562,
"spent": false,
"global_index": 214803,
"amount": 3.0
},
{
"tx_hash": "<e8409a93edeed9f6c67e6716bb180d9593e8beafa63d51facf68bee233bf694d>",
"tx_size": 525,
"spent": false,
"global_index": 165783,
"amount": 0.08
},
{
"tx_hash": "<e8409a93edeed9f6c67e6716bb180d9593e8beafa63d51facf68bee233bf694d>",
"tx_size": 525,
"spent": false,
"global_index": 375952,
"amount": 0.7
}
]
},
"id": "0"
}
Basic example 4: make a transaction
import requests
import json
import os
import binascii
def main():
"""DONT RUN IT without changing the destination address!!!"""
# simple wallet is running on the localhost and port of 18082
url = "http://localhost:18082/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
destination_address = "489MAxaT7xXP3Etjk2suJT1uDYZU6cqFycsau2ynCTBacncWVEwe9eYFrAD6BqTn4Y2KMs7maX75iX1UFwnJNG5G88wxKoj"
# amount of xmr to send
amount = 0.54321
# cryptonote amount format is different then
# that normally used by people.
# thus the float amount must be changed to
# something that cryptonote understands
int_amount = int(get_amount(amount))
# just to make sure that amount->coversion->back
# gives the same amount as in the initial number
assert amount == float(get_money(str(int_amount))), "Amount conversion failed"
# send specified xmr amount to the given destination_address
recipents = [{"address": destination_address,
"amount": int_amount}]
# using given mixin
mixin = 4
# get some random payment_id
payment_id = get_payment_id()
# simplewallet' procedure/method to call
rpc_input = {
"method": "transfer",
"params": {"destinations": recipents,
"mixin": mixin,
"payment_id" : payment_id}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# print the payment_id
print("#payment_id: ", payment_id)
# pretty print json output
print(json.dumps(response.json(), indent=4))
def get_amount(amount):
"""encode amount (float number) to the cryptonote format. Hope its correct.
Based on C++ code:
https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L211
"""
CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12
str_amount = str(amount)
fraction_size = 0
if '.' in str_amount:
point_index = str_amount.index('.')
fraction_size = len(str_amount) - point_index - 1
while fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT and '0' == str_amount[-1]:
print(44)
str_amount = str_amount[:-1]
fraction_size = fraction_size - 1
if CRYPTONOTE_DISPLAY_DECIMAL_POINT < fraction_size:
return False
str_amount = str_amount[:point_index] + str_amount[point_index+1:]
if not str_amount:
return False
if fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT:
str_amount = str_amount + '0'*(CRYPTONOTE_DISPLAY_DECIMAL_POINT - fraction_size)
return str_amount
def get_money(amount):
"""decode cryptonote amount format to user friendly format. Hope its correct.
Based on C++ code:
https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L751
"""
CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12
s = amount
if len(s) < CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1:
# add some trailing zeros, if needed, to have constant width
s = '0' * (CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1 - len(s)) + s
idx = len(s) - CRYPTONOTE_DISPLAY_DECIMAL_POINT
s = s[0:idx] + "." + s[idx:]
return s
def get_payment_id():
"""generate random payment_id
generate some random payment_id for the
transactions
payment_id is 32 bytes (64 hexadecimal characters)
thus we first generate 32 random byte array
which is then change to string representation, since
json will not not what to do with the byte array.
"""
random_32_bytes = os.urandom(32)
payment_id = "".join(map(chr, binascii.hexlify(random_32_bytes)))
return payment_id
if __name__ == "__main__":
main()
Generated output:
#payment_id: 4926869b6b5d50b24cb59f08fd76826cacdf76201b2d4648578fe610af7f786e
{
"id": "0",
"jsonrpc": "2.0",
"result": {
"tx_key": "",
"tx_hash": "<04764ab4855b8a9f9c42d99e19e1c40956a502260123521ca3f6488dd809797a>"
}
}
Other examples are here
bitmonreod
The baisc bitmonerod
rpc calls are as follows:
- getheight
- query_key
- mining_status
- getlastblockheader
- getblockheaderbyhash
- getblockheaderbyheight
- getblock
- get_info
- get_connections
Prerequsits
Before executing this code make sure that bitmonerod
is running.
Just like before, the code was written, tested and executed on Ubuntu 15.10 with
Python 3.4.3 and it requires the Requests package.
Basic example 1: get a mining status
import requests
import json
def main():
# bitmonerod' is running on the localhost and port of 18081
url = "http://localhost:18081/mining_status"
# standard json header
headers = {'content-type': 'application/json'}
# execute the rpc request
response = requests.post(
url,
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Generated output:
{
"status": "OK",
"threads_count": 2,
"speed": 117,
"active": true,
"address": "48daf1rG3hE1Txapcsxh6WXNe9MLNKtu7W7tKTivtSoVLHErYzvdcpea2nSTgGkz66RFP4GKVAsTV14v6G3oddBTHfxP6tU"
}
Basic example 2: get block header having a block hash
import requests
import json
def main():
# bitmonerod is running on the localhost and port of 18081
url = "http://localhost:18081/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# the block to get
block_hash = 'd78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6'
# bitmonerod' procedure/method to call
rpc_input = {
"method": "getblockheaderbyhash",
"params": {"hash": block_hash}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Generated output:
{
"result": {
"status": "OK",
"block_header": {
"difficulty": 756932534,
"height": 796743,
"nonce": 8389,
"depth": 46,
"orphan_status": false,
"hash": "d78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6",
"timestamp": 1445741816,
"major_version": 1,
"minor_version": 0,
"prev_hash": "dff9c6299c84f945fabde9e96afa5d44f3c8fa88835fb87a965259c46694a2cd",
"reward": 8349972377827
}
},
"jsonrpc": "2.0",
"id": "0"
}
Basic example 3: get full block information
import requests
import json
def main():
# bitmonerod is running on the localhost and port of 18082
url = "http://localhost:18081/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# the block to get
block_hash = 'd78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6'
# bitmonerod' procedure/method to call
rpc_input = {
"method": "getblock",
"params": {"hash": block_hash}
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# the response will contain binary blob. For some reason
# python's json encoder will crash trying to parse such
# response. Thus, its better to remove it from the response.
response_json_clean = json.loads(
"\n".join(filter(
lambda l: "blob" not in l, response.text.split("\n")
)))
# pretty print json output
print(json.dumps(response_json_clean, indent=4))
if __name__ == "__main__":
main()
Generated output:
{
"jsonrpc": "2.0",
"result": {
"block_header": {
"difficulty": 756932534,
"major_version": 1,
"height": 796743,
"prev_hash": "dff9c6299c84f945fabde9e96afa5d44f3c8fa88835fb87a965259c46694a2cd",
"depth": 166,
"reward": 8349972377827,
"minor_version": 0,
"timestamp": 1445741816,
"nonce": 8389,
"orphan_status": false,
"hash": "d78e2d024532d8d8f9c777e2572623fd0f229d72d9c9c9da3e7cb841a3cb73c6"
},
"json": "{\n \"major_version\": 1, \n \"minor_version\": 0, \n \"timestamp\": 1445741816, \n \"prev_id\": \"dff9c6299c84f945fabde9e96afa5d44f3c8fa88835fb87a965259c46694a2cd\", \n \"nonce\": 8389, \n \"miner_tx\": {\n \"version\": 1, \n \"unlock_time\": 796803, \n \"vin\": [ {\n \"gen\": {\n \"height\": 796743\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 9972377827, \n \"target\": {\n \"key\": \"aecebf2757be84a2d986052607ec3114969f7c9e128a051f5e13f2304287733d\"\n }\n }, {\n \"amount\": 40000000000, \n \"target\": {\n \"key\": \"c3a6d449f3fa837edbbc6beac8bc0405c6340c4e39418164b4aa1fa2202573f2\"\n }\n }, {\n \"amount\": 300000000000, \n \"target\": {\n \"key\": \"cfce614b779ab2705fc5f94a022eb983a2960ba9da02d61f430e988128236b0a\"\n }\n }, {\n \"amount\": 8000000000000, \n \"target\": {\n \"key\": \"b445b474d19ae555e048762e12ac8c406a4a6d7b0f37993dc8dabe7a31ef65b8\"\n }\n }\n ], \n \"extra\": [ 1, 243, 56, 214, 120, 176, 255, 133, 1, 251, 134, 27, 135, 49, 198, 55, 249, 146, 222, 116, 48, 103, 249, 229, 195, 120, 162, 127, 62, 35, 57, 231, 51, 2, 8, 0, 0, 0, 0, 25, 79, 41, 47\n ], \n \"signatures\": [ ]\n }, \n \"tx_hashes\": [ \"cc283dcae267c622d685b3e5f8e72aaba807dad0bb2d4170521af57c50be8165\", \"d2873b1c1800ce04434c663893a16417e8717015e9686914166f7957c5eabd68\"\n ]\n}",
"tx_hashes": [
"cc283dcae267c622d685b3e5f8e72aaba807dad0bb2d4170521af57c50be8165",
"d2873b1c1800ce04434c663893a16417e8717015e9686914166f7957c5eabd68"
],
"status": "OK"
},
"id": "0"
}
Basic example 4: get blockchain information
import requests
import json
def main():
# bitmonerod is running on the localhost and port of 18082
url = "http://localhost:18081/json_rpc"
# standard json header
headers = {'content-type': 'application/json'}
# bitmonerod' procedure/method to call
rpc_input = {
"method": "get_info"
}
# add standard rpc values
rpc_input.update({"jsonrpc": "2.0", "id": "0"})
# execute the rpc request
response = requests.post(
url,
data=json.dumps(rpc_input),
headers=headers)
# pretty print json output
print(json.dumps(response.json(), indent=4))
if __name__ == "__main__":
main()
Generated output:
{
"jsonrpc": "2.0",
"result": {
"status": "OK",
"alt_blocks_count": 0,
"difficulty": 692400878,
"height": 797031,
"tx_pool_size": 1,
"grey_peerlist_size": 3447,
"outgoing_connections_count": 12,
"tx_count": 492488,
"white_peerlist_size": 253,
"target_height": 796995,
"incoming_connections_count": 0
},
"id": "0"
}
More examples hopefully coming soon.
How can you help?
Constructive criticism, code and website edits, and new examples are always welcome.
They can be made through gihub.
Some Monero are also welcome:
48daf1rG3hE1Txapcsxh6WXNe9MLNKtu7W7tKTivtSoVLHErYzvdcpea2nSTgGkz66RFP4GKVAsTV14v6G3oddBTHfxP6tU