├── tests ├── __init__.py ├── data │ ├── test_jsonrpcdaemon │ │ ├── test_mining.json │ │ ├── test_save_bc.json │ │ ├── test_set_log_level.json │ │ ├── test_stop_daemon.json │ │ ├── test_set_log_hash_rate_mining.json │ │ ├── test_in_peers.json │ │ ├── test_out_peers.json │ │ ├── test_set_log_hash_rate_notmining.json │ │ ├── test_in_peers_unlimited.json │ │ ├── test_out_peers_unlimited.json │ │ ├── test_relay_tx.json │ │ ├── test_set_log_categories_empty.json │ │ ├── test_get_limit.json │ │ ├── test_set_log_categories_default.json │ │ ├── test_set_log_categories_multiple.json │ │ ├── test_set_bans.json │ │ ├── test_flush_txpool.json │ │ ├── test_invalid_param.json │ │ ├── test_submit_block_failure.json │ │ ├── test_method_not_found.json │ │ ├── test_on_get_block_hash_2000000.json │ │ ├── test_get_height_2294632.json │ │ ├── test_get_block_count_2287024.json │ │ ├── test_get_version.json │ │ ├── test_is_key_image_spent.json │ │ ├── test_update_check_none.json │ │ ├── 0e8fa9202e0773333360e5b9e8fb8e94272c16a8a58b6fe7cf3b4327158e3a44.tx │ │ ├── test_get_fee_estimate.json │ │ ├── test_get_alternate_chains.json │ │ ├── test_hard_fork_info.json │ │ ├── test_get_coinbase_tx_sum_2200000.json │ │ ├── test_send_transaction.json │ │ ├── test_get_outs_single.json │ │ ├── test_mining_status_off.json │ │ ├── test_get_output_histogram_1and5.json │ │ ├── test_mining_status_on.json │ │ ├── test_get_alt_blocks_hashes_doc_example.json │ │ ├── test_get_bans.json │ │ ├── test_get_last_block_header_BUSY.json │ │ ├── test_get_last_block_header_success.json │ │ ├── test_get_block_header_by_height_2288495.json │ │ ├── test_get_block_header_by_hash_90f6fd3f.json │ │ ├── test_basic_info-get_info.json │ │ ├── test_get_transaction_pool_stats.json │ │ ├── test_get_outs_multiple.json │ │ ├── test_get_info.json │ │ ├── test_get_block_template_481SgRxo_64.json │ │ ├── test_block-423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89.json │ │ ├── test_transactions_single_pruned.json │ │ └── test_get_block_2288515.json │ ├── test_outputs │ │ ├── test_v2_single_output_with_fake_amount_to_subaddr-wallet-01-query_key.json │ │ ├── test_v2_single_output_with_fake_amount_to_subaddr-wallet-00-get_accounts.json │ │ ├── test_v2_single_output_with_fake_amount_to_subaddr-wallet-02-addresses-account-0.json │ │ ├── test_viewtags-wallet-20-query_key.json │ │ ├── test_multiple_outputs-wallet-01-query_key.json │ │ ├── test_v2_single_output-wallet-01-query_key.json │ │ ├── test_v2_single_output_with_fake_amount_to_master-wallet-01-query_key.json │ │ ├── test_multiple_outputs-wallet-02-addresses-account-1.json │ │ ├── test_viewtags-wallet-00-get_accounts.json │ │ ├── test_v2_single_output-wallet-00-get_accounts.json │ │ ├── test_v2_single_output_with_fake_amount_to_master-wallet-00-get_accounts.json │ │ ├── test_coinbase_own_output-dc0861.json │ │ ├── test_multiple_outputs-wallet-00-get_accounts.json │ │ ├── test_viewtags-wallet-40-getaddress.json │ │ ├── test_coinbase_no_own_output-26dcb5.json │ │ ├── test_v2_single_output-wallet-02-addresses-account-0.json │ │ ├── test_v2_single_output_with_fake_amount_to_master-wallet-02-addresses-account-0.json │ │ ├── test_v2_single_output-daemon-00-get_transactions.json │ │ ├── test_v2_single_output_with_fake_amount_to_master-daemon-00-get_transactions.json │ │ └── test_v2_single_output_with_fake_amount_to_subaddr-daemon-00-get_transactions.json │ ├── test_jsonrpcwallet │ │ ├── test_incoming_by_tx_id-e0b15-get_transfer_by_txid.json │ │ ├── test_account_creation-20-getbalance.json │ │ ├── test_account_creation-10-create_account.json │ │ ├── test_new_address-20-new_address_account_1.json │ │ ├── test_new_address-10-new_address_account_0.json │ │ ├── test_address-10-getaddress.json │ │ ├── test_account_creation-00-get_accounts.json │ │ ├── test_address-00-get_accounts.json │ │ ├── test_account_creation-30-get_accounts.json │ │ ├── test_new_address-00-get_accounts.json │ │ ├── test_outgoing_by_tx_id-00-get_accounts.json │ │ ├── test_incoming_by_tx_id-00-get_accounts.json │ │ ├── test_incoming_from_self__issue_71-00-get_accounts.json │ │ ├── test_multiple_destinations-accounts.json │ │ ├── test_address_balance-30-get_balance-0-2.json │ │ ├── test_multiple_destinations-incoming.json │ │ ├── test_address_balance-20-get_balance-noargs.json │ │ ├── test_incoming_by_tx_id-31b52-get_transfer_by_txid.json │ │ ├── test_incoming_by_tx_id-30-get_transfer_by_txid.json │ │ ├── test_incoming_by_tx_id-55e75-get_transfer_by_txid.json │ │ ├── test_incoming_from_self__issue_71-1a75f-get_transfer_by_txid.json │ │ ├── test_outgoing_by_tx_id-afaf0-get_transfer_by_txid.json │ │ ├── test_outgoing_by_tx_id-eda89-get_transfer_by_txid.json │ │ ├── test_address_balance-00-get_accounts.json │ │ ├── test_outgoing_by_tx_id-362c3-get_transfer_by_txid.json │ │ ├── test_incoming_by_tx_id-7ab84-get_transfer_by_txid.json │ │ └── test_sweep_all-00-get_accounts.json │ └── test_offline │ │ ├── mainnet-subaddrs.json │ │ ├── stagenet-subaddrs.json │ │ └── testnet-subaddrs.json ├── utils.py ├── base.py ├── test_base58.py ├── test_numbers.py ├── test_block.py └── test_offline.py ├── monero ├── backends │ ├── __init__.py │ ├── jsonrpc │ │ ├── __init__.py │ │ └── exceptions.py │ └── offline.py ├── __init__.py ├── const.py ├── wordlists │ ├── __init__.py │ └── wordlist.py ├── ed25519.py ├── keccak.py ├── exceptions.py ├── block.py ├── numbers.py ├── daemon.py ├── transaction │ └── extra.py └── base58.py ├── MANIFEST.in ├── setup.cfg ├── requirements.txt ├── docs ├── source │ ├── exceptions.rst │ ├── misc.rst │ ├── index.rst │ ├── authors.rst │ ├── license.rst │ ├── backends.rst │ ├── outputs.rst │ ├── wallet.rst │ ├── release_notes.rst │ ├── quickstart.rst │ ├── seed.rst │ └── conf.py └── Makefile ├── test_requirements.txt ├── .gitignore ├── .editorconfig ├── .bumpversion.cfg ├── .pre-commit-config.yaml ├── .travis.yml ├── LICENSE.txt ├── utils ├── dumptx.py ├── pushtx.py ├── daemoninfo.py ├── transfer.py └── walletdump.py ├── setup.py └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /monero/backends/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *requirements*.txt 2 | -------------------------------------------------------------------------------- /monero/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1.1" 2 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_mining.json: -------------------------------------------------------------------------------- 1 | {"status": "OK", "untrusted": false} -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | 4 | [tool:pytest] 5 | rootdir=tests 6 | addopts=--cov=monero 7 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_save_bc.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "OK", 3 | "untrusted": false 4 | } 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodomex~=3.14 2 | pynacl~=1.4 3 | pysocks~=1.7 4 | requests 5 | ipaddress 6 | varint 7 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_set_log_level.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "OK", 3 | "untrusted": false 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_stop_daemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "OK", 3 | "untrusted": false 4 | } 5 | -------------------------------------------------------------------------------- /docs/source/exceptions.rst: -------------------------------------------------------------------------------- 1 | Exceptions 2 | ========== 3 | 4 | .. automodule:: monero.exceptions 5 | :members: 6 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_set_log_hash_rate_mining.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "OK", 3 | "untrusted": false 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_in_peers.json: -------------------------------------------------------------------------------- 1 | { 2 | "in_peers": 128, 3 | "status": "OK", 4 | "untrusted": false 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_out_peers.json: -------------------------------------------------------------------------------- 1 | { 2 | "out_peers": 16, 3 | "status": "OK", 4 | "untrusted": false 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_set_log_hash_rate_notmining.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "NOT MINING", 3 | "untrusted": false 4 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_in_peers_unlimited.json: -------------------------------------------------------------------------------- 1 | { 2 | "in_peers": 4294967295, 3 | "status": "OK", 4 | "untrusted": false 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_out_peers_unlimited.json: -------------------------------------------------------------------------------- 1 | { 2 | "out_peers": 4294967295, 3 | "status": "OK", 4 | "untrusted": false 5 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_relay_tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0", 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "status": "OK" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_set_log_categories_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": "", 3 | "status": "OK", 4 | "untrusted": false 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_limit.json: -------------------------------------------------------------------------------- 1 | { 2 | "limit_down": 8192, 3 | "limit_up": 2048, 4 | "status": "OK", 5 | "untrusted": false 6 | } 7 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_set_log_categories_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": "default:INFO", 3 | "status": "OK", 4 | "untrusted": false 5 | } 6 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | black~=22.3 2 | coverage~=6.3 3 | coveralls~=3.3 4 | pip>=9 5 | pytest-cov~=3.0 6 | pytest-runner~=5.2 7 | pytest~=7.1 8 | responses~=0.20 9 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_set_log_categories_multiple.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": "logging:INFO,net:FATAL", 3 | "status": "OK", 4 | "untrusted": false 5 | } -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_subaddr-wallet-01-query_key.json: -------------------------------------------------------------------------------- 1 | test_v2_single_output_with_fake_amount_to_master-wallet-01-query_key.json -------------------------------------------------------------------------------- /monero/backends/jsonrpc/__init__.py: -------------------------------------------------------------------------------- 1 | from .wallet import JSONRPCWallet 2 | from .daemon import JSONRPCDaemon 3 | from .exceptions import RPCError, Unauthorized, MethodNotFound 4 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_set_bans.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "status": "OK", 6 | "untrusted": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_subaddr-wallet-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | test_v2_single_output_with_fake_amount_to_master-wallet-00-get_accounts.json -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_flush_txpool.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "status": "OK", 6 | "untrusted": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_invalid_param.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "code": -32602, 4 | "message": "Invalid params" 5 | }, 6 | "id": "0", 7 | "jsonrpc": "2.0" 8 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_submit_block_failure.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0", 3 | "jsonrpc": "2.0", 4 | "error": { 5 | "code": -7, 6 | "message": "Block not accepted" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_subaddr-wallet-02-addresses-account-0.json: -------------------------------------------------------------------------------- 1 | test_v2_single_output_with_fake_amount_to_master-wallet-02-addresses-account-0.json -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_method_not_found.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "code": -32601, 4 | "message": "Method not found" 5 | }, 6 | "id": "", 7 | "jsonrpc": "2.0" 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_on_get_block_hash_2000000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": "dc2ef85b049311814742f543469e3ec1b8d589e68434d9f220ce41072c69c39e" 5 | } 6 | -------------------------------------------------------------------------------- /docs/source/misc.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous functions, types and constants 2 | ============================================ 3 | 4 | API reference 5 | ------------- 6 | 7 | .. automodule:: monero.numbers 8 | :members: 9 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_height_2294632.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "228c0538b7ba7d28fdd58ed310326db61ea052038bdb42652f6e1852cf666325", 3 | "height": 2294632, 4 | "status": "OK", 5 | "untrusted": false 6 | } -------------------------------------------------------------------------------- /tests/data/test_outputs/test_viewtags-wallet-20-query_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "key": "31c8c8582bffbbe823c431069cbf27e5b3d0d8c8062f8e909eafd71116840b09" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_block_count_2287024.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "count": 2287024, 6 | "status": "OK", 7 | "untrusted": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_by_tx_id-e0b15-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "code": -8, 4 | "message": "Transaction not found." 5 | }, 6 | "id": 0, 7 | "jsonrpc": "2.0" 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_multiple_outputs-wallet-01-query_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "key": "e507923516f52389eae889b6edc182ada82bb9354fb405abedbe0772a15aea0a" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output-wallet-01-query_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "key": "a759f8631116a607e0d905c09c633e320825d3a05e2b5fc54ab5f812f01a1d04" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_version.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "release": true, 6 | "status": "OK", 7 | "untrusted": false, 8 | "version": 196613 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_is_key_image_spent.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "spent_status": [ 4 | 1, 5 | 1, 6 | 0, 7 | 2 8 | ], 9 | "status": "OK", 10 | "top_hash": "", 11 | "untrusted": false 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.editorconfig 3 | !.gitignore 4 | !.travis.yml 5 | !.pre-commit-config.yaml 6 | !.bumpversion.cfg 7 | *.py[co] 8 | *~ 9 | *.bak 10 | *.swp 11 | docs/build/* 12 | .venv 13 | venv 14 | build/ 15 | dist/ 16 | *.egg-info/ 17 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_update_check_none.json: -------------------------------------------------------------------------------- 1 | { 2 | "auto_uri": "", 3 | "hash": "", 4 | "path": "", 5 | "status": "OK", 6 | "untrusted": false, 7 | "update": false, 8 | "user_uri": "", 9 | "version": "" 10 | } 11 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_account_creation-20-getbalance.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "balance": 0, 6 | "multisig_import_needed": false, 7 | "unlocked_balance": 0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_master-wallet-01-query_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "key": "a759f8631116a607e0d905c09c633e320825d3a05e2b5fc54ab5f812f01a1d04" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/0e8fa9202e0773333360e5b9e8fb8e94272c16a8a58b6fe7cf3b4327158e3a44.tx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monero-ecosystem/monero-python/HEAD/tests/data/test_jsonrpcdaemon/0e8fa9202e0773333360e5b9e8fb8e94272c16a8a58b6fe7cf3b4327158e3a44.tx -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_fee_estimate.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "credits": 0, 6 | "fee": 7790, 7 | "quantization_mask": 10000, 8 | "status": "OK", 9 | "top_hash": "", 10 | "untrusted": false 11 | } 12 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_account_creation-10-create_account.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "account_index": 1, 6 | "address": "75oMNpEDZNZhe9zBuKP4i6RpknDzAzM7t64Kq6nToUsJZms13tUucewKfZpdaQ9sNVYiMhiDyZbZR7MxbTCjq7D8N9CCo5k" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_new_address-20-new_address_account_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "address": "74PY6ZcC4N5U4MN4aTPQE58d35zGsFYK17NBx9GA66Bc37V5q6UU7eG6uLExg5m2TH5DJZc9BuEs64jEStTSV2PNLt2Y3wb", 6 | "address_index": 3 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_new_address-10-new_address_account_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "address": "7AEBRUmNcjhUjiqdVpeKKYiAVZ216AYdhBFx8UUfjPhWdKujoosnsUtHCohLcYWUXFdNiqnBsMmCFCyDkSmat3Ys4H4yHUp", 6 | "address_index": 232 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | end_of_line = lf 11 | charset = utf-8 12 | max_line_length = 120 13 | 14 | [*.yml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /monero/backends/jsonrpc/exceptions.py: -------------------------------------------------------------------------------- 1 | from ... import exceptions 2 | 3 | 4 | class RPCError(exceptions.BackendException): 5 | pass 6 | 7 | 8 | class Unauthorized(RPCError): 9 | pass 10 | 11 | 12 | class MethodNotFound(RPCError): 13 | pass 14 | 15 | 16 | class RestrictedRPC(RPCError): 17 | pass 18 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.1.1 3 | parse = (?P\d+)\.(?P\d+)(?:\.(?P\d+))? 4 | serialize = 5 | {major}.{minor}.{patch} 6 | {major}.{minor} 7 | 8 | [bumpversion:file:README.rst] 9 | 10 | [bumpversion:file:monero/__init__.py] 11 | 12 | [bumpversion:file:docs/source/conf.py] 13 | -------------------------------------------------------------------------------- /monero/const.py: -------------------------------------------------------------------------------- 1 | NET_MAIN = "main" 2 | NET_STAGE = "stage" 3 | NET_TEST = "test" 4 | 5 | NETS = (NET_MAIN, NET_TEST, NET_STAGE) 6 | MASTERADDR_NETBYTES = (18, 53, 24) 7 | SUBADDR_NETBYTES = (42, 63, 36) 8 | INTADDRR_NETBYTES = (19, 54, 25) 9 | 10 | PRIO_UNIMPORTANT = 1 11 | PRIO_NORMAL = 2 12 | PRIO_ELEVATED = 3 13 | PRIO_PRIORITY = 4 14 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_alternate_chains.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0", 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "chains": [{ 6 | "block_hash": "697cf03c89a9b118f7bdf11b1b3a6a028d7b3617d2d0ed91322c5709acf75625", 7 | "difficulty": 14114729638300280, 8 | "height": 1562062, 9 | "length": 2 10 | }], 11 | "status": "OK" 12 | } 13 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_hard_fork_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "credits": 0, 6 | "earliest_height": 2210720, 7 | "enabled": true, 8 | "state": 2, 9 | "status": "OK", 10 | "threshold": 0, 11 | "top_hash": "", 12 | "untrusted": false, 13 | "version": 14, 14 | "votes": 10080, 15 | "voting": 14, 16 | "window": 10080 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | class ClassPropertyDescriptor(object): 2 | """Based on https://stackoverflow.com/questions/5189699/how-to-make-a-class-property""" 3 | 4 | def __init__(self, fget): 5 | self.fget = fget 6 | 7 | def __get__(self, obj, klass): 8 | if klass is None: 9 | klass = type(obj) 10 | return self.fget.__get__(obj, klass)() 11 | 12 | 13 | def classproperty(func): 14 | return ClassPropertyDescriptor(classmethod(func)) 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: 2 | ^(build/|dist/|docs/|htmlcov/|venv/) 3 | repos: 4 | - repo: https://github.com/psf/black 5 | rev: 22.3.0 6 | hooks: 7 | - id: black 8 | language_version: python3 9 | #- repo: https://github.com/pycqa/isort 10 | # rev: 5.10.1 11 | # hooks: 12 | # - id: isort 13 | # name: isort (python) 14 | #- repo: https://gitlab.com/PyCQA/flake8 15 | # rev: 4.0.1 16 | # hooks: 17 | # - id: flake8 18 | # args: [--max-line-length=127] 19 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_coinbase_tx_sum_2200000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "credits": 0, 6 | "emission_amount": 139291580971286, 7 | "emission_amount_top64": 0, 8 | "fee_amount": 505668215000, 9 | "fee_amount_top64": 0, 10 | "status": "OK", 11 | "top_hash": "", 12 | "untrusted": false, 13 | "wide_emission_amount": "0x7eaf59343916", 14 | "wide_fee_amount": "0x75bc2ca0d8" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_send_transaction.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "double_spend": false, 4 | "fee_too_low": false, 5 | "invalid_input": false, 6 | "invalid_output": false, 7 | "low_mixin": false, 8 | "not_rct": false, 9 | "not_relayed": false, 10 | "overspend": false, 11 | "reason": "", 12 | "sanity_check_failed": false, 13 | "status": "OK", 14 | "too_big": false, 15 | "too_few_outputs": false, 16 | "top_hash": "", 17 | "untrusted": false 18 | } 19 | -------------------------------------------------------------------------------- /monero/wordlists/__init__.py: -------------------------------------------------------------------------------- 1 | from .wordlist import get_wordlist, list_wordlists 2 | from .english import English 3 | 4 | from .chinese_simplified import ChineseSimplified 5 | from .dutch import Dutch 6 | from .esperanto import Esperanto 7 | from .french import French 8 | from .german import German 9 | from .italian import Italian 10 | from .japanese import Japanese 11 | from .lojban import Lojban 12 | from .portuguese import Portuguese 13 | from .russian import Russian 14 | from .spanish import Spanish 15 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_outs_single.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "outs": [ 4 | { 5 | "height": 1222460, 6 | "key": "9c7055cb5b790f1eebf10b7b8fbe01241eb736b5766d15554da7099bbcdc4b44", 7 | "mask": "42e37af85cddaeccbea6fe597037c9377045a682e66661260868877b9440af70", 8 | "txid": "b357374ad4636f17520b6c2fdcf0fb5e6a1185fed2aef509b19b5100d04ae552", 9 | "unlocked": true 10 | } 11 | ], 12 | "status": "OK", 13 | "top_hash": "", 14 | "untrusted": false 15 | } -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import unittest 4 | 5 | 6 | class JSONTestCase(unittest.TestCase): 7 | jsonrpc_url = "http://127.0.0.1:18088/json_rpc" 8 | data_subdir = None 9 | 10 | def _read(self, *args): 11 | path = os.path.join(os.path.dirname(__file__), "data") 12 | if self.data_subdir: 13 | path = os.path.join(path, self.data_subdir) 14 | path = os.path.join(path, *args) 15 | with open(path, "r") as fh: 16 | return json.loads(fh.read()) 17 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_address-10-getaddress.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "address": "596ETuuDVZSNox73YLctrHaAv72fBboxy3atbEMnP3QtdnGFS9KWuHYGuy831SKWLUVCgrRfWLCxuCZ2fbVGh14X7mFrefy", 6 | "addresses": [ 7 | { 8 | "address": "596ETuuDVZSNox73YLctrHaAv72fBboxy3atbEMnP3QtdnGFS9KWuHYGuy831SKWLUVCgrRfWLCxuCZ2fbVGh14X7mFrefy", 9 | "address_index": 0, 10 | "label": "Primary account", 11 | "used": true 12 | } 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_multiple_outputs-wallet-02-addresses-account-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "address": "7BC3q5ogPCfTkBHZajDdkhSLxN3wSSULEN52Q2XzGebeetyG4oumiCHJjPpSyNvP6qR2idCYiUEqmHjKwc66fmcKN4dxW5u", 6 | "addresses": [ 7 | { 8 | "address": "7BC3q5ogPCfTkBHZajDdkhSLxN3wSSULEN52Q2XzGebeetyG4oumiCHJjPpSyNvP6qR2idCYiUEqmHjKwc66fmcKN4dxW5u", 9 | "address_index": 0, 10 | "label": "(Untitled account)", 11 | "used": true 12 | } 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_mining_status_off.json: -------------------------------------------------------------------------------- 1 | { 2 | "active": false, 3 | "address": "", 4 | "bg_idle_threshold": 0, 5 | "bg_ignore_battery": false, 6 | "bg_min_idle_seconds": 0, 7 | "bg_target": 0, 8 | "block_reward": 0, 9 | "block_target": 120, 10 | "difficulty": 250236969769, 11 | "difficulty_top64": 0, 12 | "is_background_mining_enabled": false, 13 | "pow_algorithm": "RandomX", 14 | "speed": 0, 15 | "status": "OK", 16 | "threads_count": 0, 17 | "untrusted": false, 18 | "wide_difficulty": "0x3a43492329" 19 | } 20 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_account_creation-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 0, 9 | "base_address": "53NTw2x2eJH3KCrcgWAMErZb7mqN57wkVTEjRkkUUXoWCrAd513JErRFT1AC9RvEddgpxoZTVXYJG9Jez4w9x6qd5s76wdu", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 0 13 | } 14 | ], 15 | "total_balance": 0, 16 | "total_unlocked_balance": 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "2.7" 5 | - "3.6" 6 | - "3.7" 7 | - "3.8" 8 | - "3.9" 9 | - "3.10" 10 | matrix: 11 | allow_failures: 12 | python: "nightly" 13 | 14 | cache: pip 15 | before_install: 16 | - rm -rf dist && python setup.py sdist # prep package distribution 17 | install: 18 | - pip install dist/*.tar.gz # install dependencies as specified in setup.py 19 | - pip install -r test_requirements_py`echo $TRAVIS_PYTHON_VERSION | cut -f 1 -d .`.txt 20 | script: 21 | - black --check . 22 | - pytest 23 | after_success: 24 | - coveralls 25 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_viewtags-wallet-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 4000000000000, 9 | "base_address": "9sotHmY781cAChddb8JRC9Yjuiifgq381b5nepg5FKyF3EYcQfhWLfScnSoYepu2WiCriBW7oqPkc3r9DJ8M9BE5JQeKAAp", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 4000000000000 13 | } 14 | ], 15 | "total_balance": 4000000000000, 16 | "total_unlocked_balance": 4000000000000 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_address-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 21676544656446, 9 | "base_address": "596ETuuDVZSNox73YLctrHaAv72fBboxy3atbEMnP3QtdnGFS9KWuHYGuy831SKWLUVCgrRfWLCxuCZ2fbVGh14X7mFrefy", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 21479383257259 13 | } 14 | ], 15 | "total_balance": 21676544656446, 16 | "total_unlocked_balance": 21479383257259 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output-wallet-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 11709717332048, 9 | "base_address": "54LUsTyVL2haFdvkUVngGCiacaRYkjrUvfhvnF6JS2fXNL6twQUQf7PEPtf9MvRYXvhVmtzcV2MUefinDjjwVcH56xm3AHx", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 11709717332048 13 | } 14 | ], 15 | "total_balance": 11709717332048, 16 | "total_unlocked_balance": 11709717332048 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_output_histogram_1and5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "credits": 0, 6 | "histogram": [ 7 | { 8 | "amount": 1000000000000, 9 | "recent_instances": 874619, 10 | "total_instances": 874619, 11 | "unlocked_instances": 874619 12 | }, 13 | { 14 | "amount": 5000000000000, 15 | "recent_instances": 255089, 16 | "total_instances": 255089, 17 | "unlocked_instances": 255089 18 | } 19 | ], 20 | "status": "OK", 21 | "top_hash": "", 22 | "untrusted": false 23 | } 24 | } -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_master-wallet-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 11709717332048, 9 | "base_address": "54LUsTyVL2haFdvkUVngGCiacaRYkjrUvfhvnF6JS2fXNL6twQUQf7PEPtf9MvRYXvhVmtzcV2MUefinDjjwVcH56xm3AHx", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 11709717332048 13 | } 14 | ], 15 | "total_balance": 11709717332048, 16 | "total_unlocked_balance": 11709717332048 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_mining_status_on.json: -------------------------------------------------------------------------------- 1 | { 2 | "active": true, 3 | "address": "497e4umLC7pfJ5TSSuU1QY8E1Nh5h5cWfYnvvpTrYFKiQriWfVYeVn2KH8Hpp3AeDRbCSxTvZuUZ1WYd8PGLqM4r5P5hjNQ", 4 | "bg_idle_threshold": 0, 5 | "bg_ignore_battery": false, 6 | "bg_min_idle_seconds": 0, 7 | "bg_target": 0, 8 | "block_reward": 1162352277907, 9 | "block_target": 120, 10 | "difficulty": 252551179535, 11 | "difficulty_top64": 0, 12 | "is_background_mining_enabled": false, 13 | "pow_algorithm": "RandomX", 14 | "speed": 6, 15 | "status": "OK", 16 | "threads_count": 4, 17 | "untrusted": false, 18 | "wide_difficulty": "0x3acd392d0f" 19 | } 20 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_alt_blocks_hashes_doc_example.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "blks_hashes": ["9c2277c5470234be8b32382cdf8094a103aba4fcd5e875a6fc159dc2ec00e011","637c0e0f0558e284493f38a5fcca3615db59458d90d3a5eff0a18ff59b83f46f","6f3adc174a2e8082819ebb965c96a095e3e8b63929ad9be2d705ad9c086a6b1c","697cf03c89a9b118f7bdf11b1b3a6a028d7b3617d2d0ed91322c5709acf75625","d99b3cf3ac6f17157ac7526782a3c3b9537f89d07e069f9ce7821d74bd9cad0e","e97b62109a6303233dcd697fa8545c9fcbc0bf8ed2268fede57ddfc36d8c939c","70ff822066a53ad64b04885c89bbe5ce3e537cdc1f7fa0dc55317986f01d1788","b0d36b209bd0d4442b55ea2f66b5c633f522401f921f5a85ea6f113fd2988866"], 4 | "status": "OK", 5 | "untrusted": false 6 | } -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = MoneroPythonmodule 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_bans.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "bans": [ 6 | { 7 | "host": "145.239.118.5", 8 | "ip": 91680657, 9 | "seconds": 72260 10 | }, 11 | { 12 | "host": "146.59.156.116", 13 | "ip": 1956395922, 14 | "seconds": 69397 15 | }, 16 | { 17 | "host": "213.32.121.162", 18 | "ip": 2725847253, 19 | "seconds": 3564 20 | }, 21 | { 22 | "host": "51.83.81.237", 23 | "ip": 3981529907, 24 | "seconds": 70687 25 | }, 26 | { 27 | "host": "54.39.75.54", 28 | "ip": 910894902, 29 | "seconds": 80571 30 | } 31 | ], 32 | "status": "OK", 33 | "untrusted": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/test_base58.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from monero.base58 import decode, encode 4 | 5 | 6 | class Base58EncodeTestCase(unittest.TestCase): 7 | def test_encode_empty(self): 8 | self.assertEqual(encode(""), "") 9 | 10 | def test_encode_invalid_hex_length(self): 11 | with self.assertRaises(ValueError) as cm: 12 | encode("abcde") 13 | self.assertEqual(str(cm.exception), "Hex string has invalid length: 5") 14 | 15 | 16 | class Base58DecodeTestCase(unittest.TestCase): 17 | def test_decode_empty(self): 18 | self.assertEqual(decode(""), "") 19 | 20 | def test_decode_invalid_length_block(self): 21 | with self.assertRaises(ValueError) as cm: 22 | decode("f") 23 | self.assertEqual(str(cm.exception), "Invalid encoded length: 1") 24 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_last_block_header_BUSY.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "block_header": { 6 | "block_size": 0, 7 | "cumulative_difficulty": 0, 8 | "cumulative_difficulty_top64": 0, 9 | "depth": 0, 10 | "difficulty": 0, 11 | "difficulty_top64": 0, 12 | "hash": "", 13 | "height": 0, 14 | "major_version": 0, 15 | "miner_tx_hash": "", 16 | "minor_version": 0, 17 | "nonce": 0, 18 | "num_txes": 0, 19 | "orphan_status": false, 20 | "pow_hash": "", 21 | "prev_hash": "", 22 | "reward": 0, 23 | "timestamp": 0, 24 | "wide_cumulative_difficulty": "", 25 | "wide_difficulty": "" 26 | }, 27 | "credits": 0, 28 | "status": "BUSY", 29 | "top_hash": "", 30 | "untrusted": false 31 | } 32 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_account_creation-30-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 0, 9 | "base_address": "53NTw2x2eJH3KCrcgWAMErZb7mqN57wkVTEjRkkUUXoWCrAd513JErRFT1AC9RvEddgpxoZTVXYJG9Jez4w9x6qd5s76wdu", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 0 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 0, 17 | "base_address": "75oMNpEDZNZhe9zBuKP4i6RpknDzAzM7t64Kq6nToUsJZms13tUucewKfZpdaQ9sNVYiMhiDyZbZR7MxbTCjq7D8N9CCo5k", 18 | "label": "account 1", 19 | "tag": "", 20 | "unlocked_balance": 0 21 | } 22 | ], 23 | "total_balance": 0, 24 | "total_unlocked_balance": 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_new_address-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 111141601989972, 9 | "base_address": "56cXYWG13YKaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkeUVogGN3", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 111141601989972 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 1000000000000, 17 | "base_address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 18 | "label": "Untitled account", 19 | "tag": "", 20 | "unlocked_balance": 1000000000000 21 | } 22 | ], 23 | "total_balance": 112141601989972, 24 | "total_unlocked_balance": 112141601989972 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_outgoing_by_tx_id-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 130141601989972, 9 | "base_address": "56cXYWG13YKaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkeUVogGN3", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 123141601989972 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 458323130000, 17 | "base_address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 18 | "label": "Untitled account", 19 | "tag": "", 20 | "unlocked_balance": 458323130000 21 | } 22 | ], 23 | "total_balance": 130599925119972, 24 | "total_unlocked_balance": 123599925119972 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_coinbase_own_output-dc0861.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "unlock_time": 518207, 4 | "vin": [ 5 | { 6 | "gen": { 7 | "height": 518147 8 | } 9 | } 10 | ], 11 | "vout": [ 12 | { 13 | "amount": 13515927959357, 14 | "target": { 15 | "key": "5b695861b1f0b409e1e51f0fed323fbb9dc2fe020146c060f85520e96468659d" 16 | } 17 | } 18 | ], 19 | "extra": [ 20 | 1, 21 | 220, 22 | 188, 23 | 34, 24 | 119, 25 | 62, 26 | 166, 27 | 51, 28 | 229, 29 | 198, 30 | 46, 31 | 210, 32 | 214, 33 | 191, 34 | 54, 35 | 140, 36 | 193, 37 | 246, 38 | 219, 39 | 234, 40 | 35, 41 | 107, 42 | 202, 43 | 9, 44 | 213, 45 | 173, 46 | 245, 47 | 16, 48 | 4, 49 | 39, 50 | 197, 51 | 32, 52 | 231 53 | ], 54 | "rct_signatures": { 55 | "type": 0 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_multiple_outputs-wallet-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 13387591827179, 9 | "base_address": "56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 13387591827179 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 221310000000, 17 | "base_address": "7BC3q5ogPCfTkBHZajDdkhSLxN3wSSULEN52Q2XzGebeetyG4oumiCHJjPpSyNvP6qR2idCYiUEqmHjKwc66fmcKN4dxW5u", 18 | "label": "(Untitled account)", 19 | "tag": "", 20 | "unlocked_balance": 221310000000 21 | } 22 | ], 23 | "total_balance": 13608901827179, 24 | "total_unlocked_balance": 13608901827179 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_by_tx_id-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 119141601989972, 9 | "base_address": "56cXYWG13YKaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkeUVogGN3", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 119141601989972 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 1000000000000, 17 | "base_address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 18 | "label": "Untitled account", 19 | "tag": "", 20 | "unlocked_balance": 1000000000000 21 | } 22 | ], 23 | "total_balance": 120141601989972, 24 | "total_unlocked_balance": 120141601989972 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_from_self__issue_71-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 13387591827179, 9 | "base_address": "56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 12153186067179 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 221310000000, 17 | "base_address": "7BC3q5ogPCfTkBHZajDdkhSLxN3wSSULEN52Q2XzGebeetyG4oumiCHJjPpSyNvP6qR2idCYiUEqmHjKwc66fmcKN4dxW5u", 18 | "label": "(Untitled account)", 19 | "tag": "", 20 | "unlocked_balance": 221310000000 21 | } 22 | ], 23 | "total_balance": 13608901827179, 24 | "total_unlocked_balance": 12374496067179 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_viewtags-wallet-40-getaddress.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "address": "9sotHmY781cAChddb8JRC9Yjuiifgq381b5nepg5FKyF3EYcQfhWLfScnSoYepu2WiCriBW7oqPkc3r9DJ8M9BE5JQeKAAp", 6 | "addresses": [ 7 | { 8 | "address": "9sotHmY781cAChddb8JRC9Yjuiifgq381b5nepg5FKyF3EYcQfhWLfScnSoYepu2WiCriBW7oqPkc3r9DJ8M9BE5JQeKAAp", 9 | "address_index": 0, 10 | "label": "Primary account", 11 | "used": false 12 | }, 13 | { 14 | "address": "BgnjGyQMqyz8DTRxaAat7oVWBoncUG3PmY5rwf4VBLWY6giSVbEaZec6Ae8w6GK1ZhgfFZnCL4EfXMjL1T5mkRdKKEVqSfC", 15 | "address_index": 1, 16 | "label": "", 17 | "used": true 18 | }, 19 | { 20 | "address": "BhS5oGvXMGqJLtQFea7ip3fJiYN9s23qr3nubeHLwGrf7RDPb5qm6m75VY29TCkjKF4ENANPXmkPt3opjV27t7eyDj5PmY1", 21 | "address_index": 2, 22 | "label": "", 23 | "used": true 24 | } 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /monero/ed25519.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import nacl.bindings 3 | 4 | edwards_add = nacl.bindings.crypto_core_ed25519_add 5 | inv = nacl.bindings.crypto_core_ed25519_scalar_invert 6 | scalar_add = nacl.bindings.crypto_core_ed25519_scalar_add 7 | scalarmult_B = nacl.bindings.crypto_scalarmult_ed25519_base_noclamp 8 | scalarmult = nacl.bindings.crypto_scalarmult_ed25519_noclamp 9 | 10 | # https://github.com/monero-project/monero/blob/9f814edbd78c70c70b814ca934c1ddef58768262/src/ringct/rctTypes.h#L615 11 | H = binascii.unhexlify( 12 | "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" 13 | ) 14 | 15 | 16 | def scalarmult_H(v): 17 | return scalarmult(v, H) 18 | 19 | 20 | def scalar_reduce(v): 21 | return nacl.bindings.crypto_core_ed25519_scalar_reduce(v + (64 - len(v)) * b"\0") 22 | 23 | 24 | def public_from_secret_hex(hk): 25 | try: 26 | return binascii.hexlify(scalarmult_B(binascii.unhexlify(hk))).decode() 27 | except nacl.exceptions.RuntimeError: 28 | raise ValueError("Invalid secret key") 29 | -------------------------------------------------------------------------------- /monero/keccak.py: -------------------------------------------------------------------------------- 1 | cd_keccak = None 2 | sha3_keccak = None 3 | 4 | try: 5 | from Cryptodome.Hash import keccak as cd_keccak 6 | except ImportError: 7 | pass 8 | if not cd_keccak: 9 | try: 10 | from sha3 import keccak_256 as sha3_keccak 11 | except ImportError: 12 | raise ImportError( 13 | "Could not import 'keccak' from 'Cryptodome.Hash' nor 'keccak_256' from 'sha3'. " 14 | "Install one of those modules ('cryptodomex' is the recommended one)." 15 | ) 16 | 17 | 18 | def keccak_256(data): 19 | """ 20 | Return a hashlib-compatible Keccak 256 object for the given data. 21 | """ 22 | if cd_keccak is not None: 23 | h = cd_keccak.new(digest_bits=256) 24 | h.update(data) 25 | elif sha3_keccak is not None: 26 | h = sha3_keccak(data) 27 | else: # pragma: no cover 28 | raise RuntimeError( 29 | "SHA3 implementation is missing. Install either 'pycryptodomex' (recommended) or 'pysha3' package to provide it" 30 | ) 31 | return h 32 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_multiple_destinations-accounts.json: -------------------------------------------------------------------------------- 1 | {"id": 0, 2 | "jsonrpc": "2.0", 3 | "result": {"subaddress_accounts": [{"account_index": 0, 4 | "balance": 141601990026, 5 | "base_address": "56cXYWG13YKaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkeUVogGN3", 6 | "label": "Primary account", 7 | "tag": "", 8 | "unlocked_balance": 141601990026}, 9 | {"account_index": 1, 10 | "balance": 1000000000000, 11 | "base_address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 12 | "label": "(Untitled account)", 13 | "tag": "", 14 | "unlocked_balance": 1000000000000}], 15 | "total_balance": 1141601990026, 16 | "total_unlocked_balance": 1141601990026}} 17 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_coinbase_no_own_output-26dcb5.json: -------------------------------------------------------------------------------- 1 | { 2 | "as_hex": "", 3 | "as_json": "{\n \"version\": 2, \n \"unlock_time\": 766519, \n \"vin\": [ {\n \"gen\": {\n \"height\": 766459\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 8415513145431, \n \"target\": {\n \"key\": \"6c4a7168678f9d5c504e72c54d55a1cfc118cce9af944b6ecca10556541af056\"\n }\n }\n ], \n \"extra\": [ 1, 128, 93, 146, 154, 55, 1, 170, 165, 76, 172, 184, 227, 115, 121, 76, 30, 233, 193, 8, 228, 133, 96, 186, 122, 30, 54, 92, 179, 188, 229, 65, 243\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n}", 4 | "block_height": 766459, 5 | "block_timestamp": 1612470441, 6 | "double_spend_seen": false, 7 | "in_pool": false, 8 | "output_indices": [ 9 | 3129279 10 | ], 11 | "prunable_as_hex": "", 12 | "prunable_hash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", 13 | "pruned_as_hex": "02b7e42e01fffbe32e01d7e0af9df6f401026c4a7168678f9d5c504e72c54d55a1cfc118cce9af944b6ecca10556541af0562101805d929a3701aaa54cacb8e373794c1ee9c108e48560ba7a1e365cb3bce541f300", 14 | "tx_hash": "26dcb55c3c93a2176949fd9ec4e20a9d97ece7c420408d9353c390a909e9a7c1" 15 | } 16 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_last_block_header_success.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "block_header": { 6 | "block_size": 65605, 7 | "block_weight": 65605, 8 | "cumulative_difficulty": 86426998743673801, 9 | "cumulative_difficulty_top64": 0, 10 | "depth": 0, 11 | "difficulty": 253652891944, 12 | "difficulty_top64": 0, 13 | "hash": "a55ec867052340715c4b8b4dcd2de53bc2a195e666058d10a224037932ccdc40", 14 | "height": 2287573, 15 | "long_term_weight": 65605, 16 | "major_version": 14, 17 | "miner_tx_hash": "42219818a7f30910a89e0d0d9fc479950137b93820e5955fc071fa8f4e3c2400", 18 | "minor_version": 14, 19 | "nonce": 37920, 20 | "num_txes": 34, 21 | "orphan_status": false, 22 | "pow_hash": "", 23 | "prev_hash": "7ca630666d7040f0cadbaaf9da92db4797ef67b60ca8f15324b94236ffe0b3a8", 24 | "reward": 1181081601887, 25 | "timestamp": 1612215053, 26 | "wide_cumulative_difficulty": "0x1330ce5bf1eabc9", 27 | "wide_difficulty": "0x3b0ee3f928" 28 | }, 29 | "credits": 0, 30 | "status": "OK", 31 | "top_hash": "", 32 | "untrusted": false 33 | } 34 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_block_header_by_height_2288495.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "block_header": { 6 | "block_size": 8415, 7 | "block_weight": 8415, 8 | "cumulative_difficulty": 86651164421641270, 9 | "cumulative_difficulty_top64": 0, 10 | "depth": 3, 11 | "difficulty": 238154836806, 12 | "difficulty_top64": 0, 13 | "hash": "966c1a70358ce998e7d5fb243b155971f9bffe06030c92dbd70486d398c40c05", 14 | "height": 2288495, 15 | "long_term_weight": 8415, 16 | "major_version": 14, 17 | "miner_tx_hash": "408dd52531cab37e51db5a9a581bf25691b5534d8d0037b38e68061691b976e1", 18 | "minor_version": 14, 19 | "nonce": 1275098057, 20 | "num_txes": 5, 21 | "orphan_status": false, 22 | "pow_hash": "", 23 | "prev_hash": "47751e6eb31230e92a5ee98242aa34d79bfd48657f2727c9a9b3cbad6aee88bb", 24 | "reward": 1176760892780, 25 | "timestamp": 1612328193, 26 | "wide_cumulative_difficulty": "0x133d8c662b9d436", 27 | "wide_difficulty": "0x3773226b46" 28 | }, 29 | "credits": 0, 30 | "status": "OK", 31 | "top_hash": "", 32 | "untrusted": false 33 | } 34 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_block_header_by_hash_90f6fd3f.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "block_header": { 6 | "block_size": 17130, 7 | "block_weight": 17130, 8 | "cumulative_difficulty": 86641187797059581, 9 | "cumulative_difficulty_top64": 0, 10 | "depth": 4, 11 | "difficulty": 239025076303, 12 | "difficulty_top64": 0, 13 | "hash": "90f6fd3fe29c7518f15afd2da31734e890cc24247b5da10dc9ac2ea215ae844b", 14 | "height": 2288453, 15 | "long_term_weight": 17130, 16 | "major_version": 14, 17 | "miner_tx_hash": "5e8d9531ae078ef5630e3c9950eb768b87b31481652c2b8dafca25d57e9c0c3f", 18 | "minor_version": 14, 19 | "nonce": 1040830456, 20 | "num_txes": 11, 21 | "orphan_status": false, 22 | "pow_hash": "", 23 | "prev_hash": "a78d9e631f743806b0b8d3a70bb85758db466633eb3b4620737dd29b0548eb21", 24 | "reward": 1176972120146, 25 | "timestamp": 1612323209, 26 | "wide_cumulative_difficulty": "0x133cfb3858fc7fd", 27 | "wide_difficulty": "0x37a701384f" 28 | }, 29 | "credits": 0, 30 | "status": "OK", 31 | "top_hash": "", 32 | "untrusted": false 33 | } 34 | } -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_address_balance-30-get_balance-0-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "balance": 12982128625328954, 6 | "blocks_to_unlock": 0, 7 | "multisig_import_needed": false, 8 | "per_subaddress": [ 9 | { 10 | "account_index": 0, 11 | "address": "55LTR8KniP4LQGJSPtbYDacR7dz8RBFnsfAKMaMuwUNYX6aQbBcovzDPyrQF9KXF9tVU6Xk3K8no1BywnJX6GvZX8yJsXvt", 12 | "address_index": 0, 13 | "balance": 12943277373064573, 14 | "blocks_to_unlock": 0, 15 | "label": "Primary account", 16 | "num_unspent_outputs": 1291, 17 | "time_to_unlock": 0, 18 | "unlocked_balance": 12943277373064573 19 | }, 20 | { 21 | "account_index": 0, 22 | "address": "75sNpRwUtekcJGejMuLSGA71QFuK1qcCVLZnYRTfQLgFU5nJ7xiAHtR5ihioS53KMe8pBhH61moraZHyLoG4G7fMER8xkNv", 23 | "address_index": 2, 24 | "balance": 800200000000, 25 | "blocks_to_unlock": 0, 26 | "label": "", 27 | "num_unspent_outputs": 5, 28 | "time_to_unlock": 0, 29 | "unlocked_balance": 800200000000 30 | } 31 | ], 32 | "time_to_unlock": 0, 33 | "unlocked_balance": 12982128625328954 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_basic_info-get_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "alt_blocks_count": 188, 6 | "block_size_limit": 600000, 7 | "block_size_median": 300000, 8 | "block_weight_limit": 600000, 9 | "block_weight_median": 300000, 10 | "bootstrap_daemon_address": "", 11 | "cumulative_difficulty": 11208345440, 12 | "database_size": 4434726912, 13 | "difficulty": 8068, 14 | "free_space": 18446744073709551615, 15 | "grey_peerlist_size": 580, 16 | "height": 294993, 17 | "height_without_bootstrap": 294993, 18 | "incoming_connections_count": 22, 19 | "mainnet": false, 20 | "nettype": "stagenet", 21 | "offline": false, 22 | "outgoing_connections_count": 5, 23 | "rpc_connections_count": 1, 24 | "stagenet": true, 25 | "start_time": 1552341929, 26 | "status": "OK", 27 | "target": 120, 28 | "target_height": 294919, 29 | "testnet": false, 30 | "top_block_hash": "e93984090c86b3b97b8a73c3fa0688acb3ea46603274e69cdbeef94b4b62fd78", 31 | "tx_count": 102229, 32 | "tx_pool_size": 2, 33 | "untrusted": false, 34 | "update_available": false, 35 | "was_bootstrap_ever_used": false, 36 | "white_peerlist_size": 110 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_transaction_pool_stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "pool_stats": { 4 | "bytes_max": 17922, 5 | "bytes_med": 1967, 6 | "bytes_min": 1452, 7 | "bytes_total": 75438, 8 | "fee_total": 586900000, 9 | "histo": [ 10 | { 11 | "bytes": 3419, 12 | "txs": 2 13 | }, 14 | { 15 | "bytes": 3423, 16 | "txs": 2 17 | }, 18 | { 19 | "bytes": 1455, 20 | "txs": 1 21 | }, 22 | { 23 | "bytes": 14851, 24 | "txs": 1 25 | }, 26 | { 27 | "bytes": 0, 28 | "txs": 0 29 | }, 30 | { 31 | "bytes": 0, 32 | "txs": 0 33 | }, 34 | { 35 | "bytes": 22776, 36 | "txs": 5 37 | }, 38 | { 39 | "bytes": 0, 40 | "txs": 0 41 | }, 42 | { 43 | "bytes": 0, 44 | "txs": 0 45 | }, 46 | { 47 | "bytes": 29514, 48 | "txs": 6 49 | } 50 | ], 51 | "histo_98pc": 0, 52 | "num_10m": 0, 53 | "num_double_spends": 0, 54 | "num_failing": 0, 55 | "num_not_relayed": 0, 56 | "oldest": 1613865632, 57 | "txs_total": 17 58 | }, 59 | "status": "OK", 60 | "top_hash": "", 61 | "untrusted": false 62 | } 63 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_multiple_destinations-incoming.json: -------------------------------------------------------------------------------- 1 | {"id": 0, 2 | "jsonrpc": "2.0", 3 | "result": {"out": [{"address": "56cXYWG13YKaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkeUVogGN3", 4 | "amount": 1000000000000, 5 | "confirmations": 66, 6 | "destinations": [{"address": "76yuj8ZcnuNhSCciBJHDzhBoGBXRoDVkeLbzD7gW2yiFc9ypd9ArzSBc2bkBHMsULU3LcXDBV681YbhLJn2bYbKq5mc2xsU", 7 | "amount": 700000000000}, 8 | {"address": "77LbfWj3yyhggccY2oLy2AcuBLqEvXKCSAYQUD6dfzBGcZXXBHgVrgy4VgBkZAaBnCdjwAC6koinjdxpXdfRjMT84neunYJ", 9 | "amount": 300000000000}], 10 | "double_spend_seen": false, 11 | "fee": 477360000, 12 | "height": 278441, 13 | "note": "", 14 | "payment_id": "0000000000000000", 15 | "subaddr_index": {"major": 0, "minor": 0}, 16 | "suggested_confirmations_threshold": 1, 17 | "timestamp": 1552346064, 18 | "txid": "f107123ae0611de2b732f4c6ad65a2c077d465a4e0708d3619dd44054b932f52", 19 | "type": "out", 20 | "unlock_time": 0}]}} 21 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_outs_multiple.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "outs": [ 4 | { 5 | "height": 1224094, 6 | "key": "fc13952b8b9c193d4c875e750e88a0da8a7d348f95c019cfde93762d68298dd7", 7 | "mask": "bf99dc047048605f6e0aeebc937477ae6e9e3143e1be1b48af225b41f809e44e", 8 | "txid": "687f9b1d6fa409a13e84c682e90127b1953e10efe679c114a01d7db77f474d50", 9 | "unlocked": true 10 | }, 11 | { 12 | "height": 1224094, 13 | "key": "23212433aec99219a823f107bcd27cb45a292d5c831b096d8e655e77e133b27e", 14 | "mask": "3ad2a785a992b1491713efc809996e7007c9fabaf962edf10961d60aaa0dace7", 15 | "txid": "687f9b1d6fa409a13e84c682e90127b1953e10efe679c114a01d7db77f474d50", 16 | "unlocked": true 17 | }, 18 | { 19 | "height": 999999, 20 | "key": "fc22266e72afb339559e8938c99ee86157bfea20cd6115c477c40a53bc173378", 21 | "mask": "02fa353aa84ea8c44c8023065d7941606b1fa5c264dccf46dc6494ebe9606f20", 22 | "txid": "2a5d456439f7ae27b5d26e493651c0e24e1d7e02b6d9d019c89d562ce0658472", 23 | "unlocked": true 24 | }, 25 | { 26 | "height": 999999, 27 | "key": "e20315663e3d278421797c4098c828cad5220849d08c3d26fee72003d4cda698", 28 | "mask": "100c6f1342b71b73edddc5492be923182f00a683488ec3a2a1c7a949cbe57768", 29 | "txid": "2a5d456439f7ae27b5d26e493651c0e24e1d7e02b6d9d019c89d562ce0658472", 30 | "unlocked": true 31 | } 32 | ], 33 | "status": "OK", 34 | "top_hash": "", 35 | "untrusted": false 36 | } -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://getmonero.org/press-kit/logos/monero-logo-symbol-on-white-480.png 2 | 3 | Python module for Monero 4 | ======================== 5 | 6 | .. warning:: **URGENT SECURITY UPDATE** 7 | The version 1.0.2 contains an urgent security update in the output recognition code. If you're 8 | using the module for scanning transactions and identifying outputs using the secret view key, 9 | UPDATE THE SOFTWARE IMMEDIATELY. 10 | Otherwise you're safe. Standard wallet operations like receiving payments, spending, address 11 | generation etc. are NOT AFFECTED. 12 | 13 | Welcome to the documentation for the ``monero`` Python module. 14 | 15 | The aim of this project is to offer a set of tools for interacting with Monero 16 | cryptocurrency in Python. It provides higher level classes representing objects 17 | from the Monero environment, like wallets, accounts, addresses, transactions. 18 | 19 | Currently it operates over JSON RPC protocol, however other backends are 20 | planned as well. 21 | 22 | Project homepage: https://github.com/monero-ecosystem/monero-python 23 | 24 | .. toctree:: 25 | :maxdepth: 2 26 | :caption: Contents: 27 | 28 | quickstart 29 | wallet 30 | address 31 | transactions 32 | daemon 33 | outputs 34 | backends 35 | seed 36 | misc 37 | exceptions 38 | release_notes 39 | license 40 | authors 41 | 42 | Indices and tables 43 | ------------------ 44 | 45 | * :ref:`genindex` 46 | * :ref:`modindex` 47 | * :ref:`search` 48 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "adjusted_time": 1612377295, 6 | "alt_blocks_count": 0, 7 | "block_size_limit": 600000, 8 | "block_size_median": 300000, 9 | "block_weight_limit": 600000, 10 | "block_weight_median": 300000, 11 | "bootstrap_daemon_address": "", 12 | "busy_syncing": false, 13 | "credits": 0, 14 | "cumulative_difficulty": 86754859378339639, 15 | "cumulative_difficulty_top64": 0, 16 | "database_size": 120378499072, 17 | "difficulty": 258886688222, 18 | "difficulty_top64": 0, 19 | "free_space": 275984478208, 20 | "grey_peerlist_size": 5000, 21 | "height": 2288923, 22 | "height_without_bootstrap": 2288923, 23 | "incoming_connections_count": 0, 24 | "mainnet": true, 25 | "nettype": "mainnet", 26 | "offline": false, 27 | "outgoing_connections_count": 12, 28 | "rpc_connections_count": 1, 29 | "stagenet": false, 30 | "start_time": 1612375367, 31 | "status": "OK", 32 | "synchronized": true, 33 | "target": 120, 34 | "target_height": 2288923, 35 | "testnet": false, 36 | "top_block_hash": "d41401b43220e54ec2567a889e0ad65196ba28d98e61b24d20491ddd060317a1", 37 | "top_hash": "", 38 | "tx_count": 11295065, 39 | "tx_pool_size": 21, 40 | "untrusted": false, 41 | "update_available": false, 42 | "version": "0.17.1.9-release", 43 | "was_bootstrap_ever_used": false, 44 | "white_peerlist_size": 1000, 45 | "wide_cumulative_difficulty": "0x1343715bfc8ef37", 46 | "wide_difficulty": "0x3c46d95dde" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/source/authors.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | * Michał Sałaban 5 | * MoneroPy Developers (``monero/base58.py`` taken from `MoneroPy`_) 6 | * thomasv@gitorious (``monero/seed.py`` based on `Electrum`_) 7 | * and other Contributors: `lalanza808`_, `cryptochangements34`_, `atward`_, `rooterkyberian`_, `brucexiu`_, `lialsoftlab`_, `moneroexamples`_, `massanchik`_, `MrClottom`_, `jeffro256`_, `sometato`_, `kayabaNerve`_, `j-berman`_. 8 | 9 | 10 | .. _`LICENSE.txt`: LICENSE.txt 11 | .. _`MoneroPy`: https://github.com/bigreddmachine/MoneroPy 12 | .. _`Electrum`: https://github.com/spesmilo/electrum 13 | 14 | .. _`lalanza808`: https://github.com/lalanza808 15 | .. _`cryptochangements34`: https://github.com/cryptochangements34 16 | .. _`atward`: https://github.com/atward 17 | .. _`rooterkyberian`: https://github.com/rooterkyberian 18 | .. _`brucexiu`: https://github.com/brucexiu 19 | .. _`lialsoftlab`: https://github.com/lialsoftlab 20 | .. _`moneroexamples`: https://github.com/moneroexamples 21 | .. _`massanchik`: https://github.com/massanchik 22 | .. _`MrClottom`: https://github.com/MrClottom 23 | .. _`jeffro256`: https://github.com/jeffro256 24 | .. _`sometato`: https://github.com/sometato 25 | .. _`kayabaNerve`: https://github.com/kayabaNerve 26 | .. _`j-berman`: https://github.com/j-berman 27 | 28 | Acknowledgements 29 | ---------------- 30 | 31 | This project has been generously funded by the donors of Monero Forum Funding System. 32 | You may see the `original project submission`_. 33 | 34 | .. _original project submission: https://forum.getmonero.org/8/funding-required/89298/comprehensive-python-module-for-handling-monero 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017 Michał Sałaban 4 | Copyright (c) 2016 The MoneroPy Developers 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_address_balance-20-get_balance-noargs.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "balance": 12982128625328954, 6 | "blocks_to_unlock": 0, 7 | "multisig_import_needed": false, 8 | "per_subaddress": [ 9 | { 10 | "account_index": 0, 11 | "address": "55LTR8KniP4LQGJSPtbYDacR7dz8RBFnsfAKMaMuwUNYX6aQbBcovzDPyrQF9KXF9tVU6Xk3K8no1BywnJX6GvZX8yJsXvt", 12 | "address_index": 0, 13 | "balance": 12943277373064573, 14 | "blocks_to_unlock": 0, 15 | "label": "Primary account", 16 | "num_unspent_outputs": 1291, 17 | "time_to_unlock": 0, 18 | "unlocked_balance": 12943277373064573 19 | }, 20 | { 21 | "account_index": 0, 22 | "address": "7BnERTpvL5MbCLtj5n9No7J5oE5hHiB3tVCK5cjSvCsYWD2WRJLFuWeKTLiXo5QJqt2ZwUaLy2Vh1Ad51K7FNgqcHgjW85o", 23 | "address_index": 1, 24 | "balance": 38051052264381, 25 | "blocks_to_unlock": 0, 26 | "label": "", 27 | "num_unspent_outputs": 75, 28 | "time_to_unlock": 0, 29 | "unlocked_balance": 38051052264381 30 | }, 31 | { 32 | "account_index": 0, 33 | "address": "75sNpRwUtekcJGejMuLSGA71QFuK1qcCVLZnYRTfQLgFU5nJ7xiAHtR5ihioS53KMe8pBhH61moraZHyLoG4G7fMER8xkNv", 34 | "address_index": 2, 35 | "balance": 800200000000, 36 | "blocks_to_unlock": 0, 37 | "label": "", 38 | "num_unspent_outputs": 5, 39 | "time_to_unlock": 0, 40 | "unlocked_balance": 800200000000 41 | } 42 | ], 43 | "time_to_unlock": 0, 44 | "unlocked_balance": 12982128625328954 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /monero/backends/offline.py: -------------------------------------------------------------------------------- 1 | from .. import exceptions 2 | from ..account import Account 3 | from ..address import Address 4 | from ..numbers import EMPTY_KEY 5 | from ..seed import Seed 6 | 7 | 8 | class WalletIsOffline(exceptions.BackendException): 9 | pass 10 | 11 | 12 | class OfflineWallet(object): 13 | """ 14 | Offline backend for Monero wallet. Provides support for address generation. 15 | """ 16 | 17 | _address = None 18 | _svk = None 19 | _ssk = EMPTY_KEY 20 | 21 | def __init__(self, address, view_key=None, spend_key=None): 22 | self._address = Address(address) 23 | self._svk = view_key or self._svk 24 | self._ssk = spend_key or self._ssk 25 | 26 | def spend_key(self): 27 | return self._ssk 28 | 29 | def view_key(self): 30 | return self._svk 31 | 32 | def seed(self): 33 | return Seed(self._ssk) 34 | 35 | def accounts(self): 36 | return [Account(self, 0)] 37 | 38 | def addresses(self, account=0, addr_indices=None): 39 | if account == 0 and (addr_indices == [0] or addr_indices is None): 40 | return [self._address] 41 | raise WalletIsOffline() # pragma: no cover (this should never happen) 42 | 43 | def is_offline(self, *_, **__): 44 | raise WalletIsOffline() 45 | 46 | address_balance = ( 47 | balances 48 | ) = ( 49 | export_key_images 50 | ) = ( 51 | export_outputs 52 | ) = ( 53 | height 54 | ) = ( 55 | import_key_images 56 | ) = ( 57 | import_outputs 58 | ) = ( 59 | new_account 60 | ) = new_address = transfer = transfers_in = transfers_out = sweep_all = is_offline 61 | -------------------------------------------------------------------------------- /docs/source/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | BSD 3-Clause License 5 | 6 | Copyright (c) 2017 Michał Sałaban 7 | 8 | Copyright (c) 2016 The MoneroPy Developers 9 | 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | * Neither the name of the copyright holder nor the names of its 23 | contributors may be used to endorse or promote products derived from 24 | this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /monero/exceptions.py: -------------------------------------------------------------------------------- 1 | class MoneroException(Exception): 2 | pass 3 | 4 | 5 | class BackendException(MoneroException): 6 | pass 7 | 8 | 9 | class NoDaemonConnection(BackendException): 10 | pass 11 | 12 | 13 | class DaemonIsBusy(BackendException): 14 | pass 15 | 16 | 17 | class AccountException(MoneroException): 18 | pass 19 | 20 | 21 | class WrongAddress(AccountException): 22 | pass 23 | 24 | 25 | class WrongPaymentId(AccountException): 26 | pass 27 | 28 | 29 | class NotEnoughMoney(AccountException): 30 | pass 31 | 32 | 33 | class NotEnoughUnlockedMoney(NotEnoughMoney): 34 | pass 35 | 36 | 37 | class AmountIsZero(AccountException): 38 | pass 39 | 40 | 41 | class TransactionNotPossible(AccountException): 42 | pass 43 | 44 | 45 | class TransactionBroadcastError(BackendException): 46 | def __init__(self, message, details=None): 47 | self.details = details 48 | super(TransactionBroadcastError, self).__init__(message) 49 | 50 | 51 | class TransactionNotFound(AccountException): 52 | pass 53 | 54 | 55 | class SignatureCheckFailed(MoneroException): 56 | pass 57 | 58 | 59 | class WalletIsNotDeterministic(MoneroException): 60 | pass 61 | 62 | 63 | class GenericTransferError(AccountException): 64 | pass 65 | 66 | 67 | class AccountIndexOutOfBound(AccountException): 68 | pass 69 | 70 | 71 | class AddressIndexOutOfBound(AccountException): 72 | pass 73 | 74 | 75 | class WalletIsWatchOnly(MoneroException): 76 | pass 77 | 78 | 79 | class TransactionIncomplete(MoneroException): 80 | pass 81 | 82 | 83 | class TransactionWithoutBlob(TransactionIncomplete): 84 | pass 85 | 86 | 87 | class TransactionWithoutJSON(TransactionIncomplete): 88 | pass 89 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_by_tx_id-31b52-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "7AwMU2kQkqseHgdVWPaD6J8QvUbomAR3ThBkyaBH3dFTTwT2CcQaZyrSetwq2TXtweHFpprTN1SmEKM2wG64oFdZQ5mqkLe", 7 | "amount": 6000000000000, 8 | "confirmations": 0, 9 | "double_spend_seen": false, 10 | "fee": 195990000, 11 | "height": 0, 12 | "note": "", 13 | "payment_id": "0000000000000000", 14 | "subaddr_index": { 15 | "major": 0, 16 | "minor": 233 17 | }, 18 | "subaddr_indices": [ 19 | { 20 | "major": 0, 21 | "minor": 233 22 | } 23 | ], 24 | "suggested_confirmations_threshold": 1, 25 | "timestamp": 1568409539, 26 | "txid": "31b527fb9c27e759d56892fef93136df1057186c5cf4e3c93c5298b70160f562", 27 | "type": "pool", 28 | "unlock_time": 0 29 | }, 30 | "transfers": [ 31 | { 32 | "address": "7AwMU2kQkqseHgdVWPaD6J8QvUbomAR3ThBkyaBH3dFTTwT2CcQaZyrSetwq2TXtweHFpprTN1SmEKM2wG64oFdZQ5mqkLe", 33 | "amount": 6000000000000, 34 | "confirmations": 0, 35 | "double_spend_seen": false, 36 | "fee": 195990000, 37 | "height": 0, 38 | "note": "", 39 | "payment_id": "0000000000000000", 40 | "subaddr_index": { 41 | "major": 0, 42 | "minor": 233 43 | }, 44 | "subaddr_indices": [ 45 | { 46 | "major": 0, 47 | "minor": 233 48 | } 49 | ], 50 | "suggested_confirmations_threshold": 1, 51 | "timestamp": 1568409539, 52 | "txid": "31b527fb9c27e759d56892fef93136df1057186c5cf4e3c93c5298b70160f562", 53 | "type": "pool", 54 | "unlock_time": 0 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_by_tx_id-30-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "7AEBRUmNcjhUjiqdVpeKKYiAVZ216AYdhBFx8UUfjPhWdKujoosnsUtHCohLcYWUXFdNiqnBsMmCFCyDkSmat3Ys4H4yHUp", 7 | "amount": 4000000000000, 8 | "confirmations": 1, 9 | "double_spend_seen": false, 10 | "fee": 195890000, 11 | "height": 409450, 12 | "note": "", 13 | "payment_id": "0000000000000000", 14 | "subaddr_index": { 15 | "major": 0, 16 | "minor": 232 17 | }, 18 | "subaddr_indices": [ 19 | { 20 | "major": 0, 21 | "minor": 232 22 | } 23 | ], 24 | "suggested_confirmations_threshold": 1, 25 | "timestamp": 1568408341, 26 | "txid": "55e758d7d259bb316551ddcdd4808711de99c30b8b5c52de3e95e563fd92d156", 27 | "type": "in", 28 | "unlock_time": 0 29 | }, 30 | "transfers": [ 31 | { 32 | "address": "7AEBRUmNcjhUjiqdVpeKKYiAVZ216AYdhBFx8UUfjPhWdKujoosnsUtHCohLcYWUXFdNiqnBsMmCFCyDkSmat3Ys4H4yHUp", 33 | "amount": 4000000000000, 34 | "confirmations": 1, 35 | "double_spend_seen": false, 36 | "fee": 195890000, 37 | "height": 409450, 38 | "note": "", 39 | "payment_id": "0000000000000000", 40 | "subaddr_index": { 41 | "major": 0, 42 | "minor": 232 43 | }, 44 | "subaddr_indices": [ 45 | { 46 | "major": 0, 47 | "minor": 232 48 | } 49 | ], 50 | "suggested_confirmations_threshold": 1, 51 | "timestamp": 1568408341, 52 | "txid": "55e758d7d259bb316551ddcdd4808711de99c30b8b5c52de3e95e563fd92d156", 53 | "type": "in", 54 | "unlock_time": 0 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_by_tx_id-55e75-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "7AEBRUmNcjhUjiqdVpeKKYiAVZ216AYdhBFx8UUfjPhWdKujoosnsUtHCohLcYWUXFdNiqnBsMmCFCyDkSmat3Ys4H4yHUp", 7 | "amount": 4000000000000, 8 | "confirmations": 1, 9 | "double_spend_seen": false, 10 | "fee": 195890000, 11 | "height": 409450, 12 | "note": "", 13 | "payment_id": "0000000000000000", 14 | "subaddr_index": { 15 | "major": 0, 16 | "minor": 232 17 | }, 18 | "subaddr_indices": [ 19 | { 20 | "major": 0, 21 | "minor": 232 22 | } 23 | ], 24 | "suggested_confirmations_threshold": 1, 25 | "timestamp": 1568408341, 26 | "txid": "55e758d7d259bb316551ddcdd4808711de99c30b8b5c52de3e95e563fd92d156", 27 | "type": "in", 28 | "unlock_time": 0 29 | }, 30 | "transfers": [ 31 | { 32 | "address": "7AEBRUmNcjhUjiqdVpeKKYiAVZ216AYdhBFx8UUfjPhWdKujoosnsUtHCohLcYWUXFdNiqnBsMmCFCyDkSmat3Ys4H4yHUp", 33 | "amount": 4000000000000, 34 | "confirmations": 1, 35 | "double_spend_seen": false, 36 | "fee": 195890000, 37 | "height": 409450, 38 | "note": "", 39 | "payment_id": "0000000000000000", 40 | "subaddr_index": { 41 | "major": 0, 42 | "minor": 232 43 | }, 44 | "subaddr_indices": [ 45 | { 46 | "major": 0, 47 | "minor": 232 48 | } 49 | ], 50 | "suggested_confirmations_threshold": 1, 51 | "timestamp": 1568408341, 52 | "txid": "55e758d7d259bb316551ddcdd4808711de99c30b8b5c52de3e95e563fd92d156", 53 | "type": "in", 54 | "unlock_time": 0 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /monero/block.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from .transaction import Transaction 3 | 4 | 5 | class Block(object): 6 | """ 7 | A Monero block. Identified by `hash` (optionaly by `height`). 8 | 9 | This class is not intended to be turned into objects by the user, 10 | it is used by backends. 11 | """ 12 | 13 | hash = None 14 | height = None 15 | timestamp = None 16 | version = None 17 | difficulty = None 18 | nonce = None 19 | orphan = False 20 | prev_hash = None 21 | reward = None 22 | blob = None 23 | 24 | transactions = None 25 | 26 | def __init__(self, **kwargs): 27 | for k in ( 28 | "hash", 29 | "height", 30 | "timestamp", 31 | "version", 32 | "difficulty", 33 | "nonce", 34 | "prev_hash", 35 | "reward", 36 | ): 37 | setattr(self, k, kwargs.get(k, getattr(self, k))) 38 | self.orphan = kwargs.get("orphan", self.orphan) 39 | self.transactions = kwargs.get("transactions", self.transactions or []) 40 | self.blob = kwargs.get("blob", self.blob) 41 | 42 | def __eq__(self, other): 43 | if isinstance(other, Block): 44 | return self.hash == other.hash 45 | elif isinstance(other, str): 46 | return self.hash == other 47 | return super(Block, self).__eq__(other) 48 | 49 | def __contains__(self, tx): 50 | if isinstance(tx, str): 51 | txid = tx 52 | elif isinstance(tx, Transaction): 53 | txid = tx.hash 54 | else: 55 | raise ValueError( 56 | "Only Transaction or hash strings may be used to test existence in Blocks, " 57 | "got '{:s}'".format(tx) 58 | ) 59 | return txid in map(operator.attrgetter("hash"), self.transactions) 60 | -------------------------------------------------------------------------------- /utils/dumptx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import argparse 3 | import json 4 | import logging 5 | import operator 6 | import re 7 | import sys 8 | 9 | from monero import exceptions 10 | from monero.backends.jsonrpc import JSONRPCDaemon 11 | from monero.daemon import Daemon 12 | 13 | 14 | def url_data(url): 15 | gs = re.compile(r"^(?P[^:\s]+)(?::(?P[0-9]+))?$").match(url).groupdict() 16 | return dict(filter(operator.itemgetter(1), gs.items())) 17 | 18 | 19 | argsparser = argparse.ArgumentParser( 20 | description="Retrieve transaction(s) from daemon and print them" 21 | ) 22 | argsparser.add_argument("tx_id", nargs="+", type=str, help="Transaction id (hash)") 23 | argsparser.add_argument( 24 | "-d", 25 | dest="daemon_rpc_url", 26 | type=url_data, 27 | default="127.0.0.1:18081", 28 | help="Daemon RPC URL [host[:port]]", 29 | ) 30 | argsparser.add_argument( 31 | "-t", dest="timeout", type=int, default=30, help="Request timeout" 32 | ) 33 | argsparser.add_argument( 34 | "-v", 35 | dest="verbosity", 36 | action="count", 37 | default=0, 38 | help="Verbosity (repeat to increase; -v for INFO, -vv for DEBUG", 39 | ) 40 | args = argsparser.parse_args() 41 | level = logging.WARNING 42 | if args.verbosity == 1: 43 | level = logging.INFO 44 | elif args.verbosity > 1: 45 | level = logging.DEBUG 46 | logging.basicConfig(level=level, format="%(asctime)-15s %(message)s") 47 | 48 | d = Daemon(JSONRPCDaemon(timeout=args.timeout, **args.daemon_rpc_url)) 49 | 50 | txs = list(d.transactions(args.tx_id)) 51 | print("Found {:d} transaction(s)".format(len(txs))) 52 | if len(txs) > 0: 53 | print("-" * 79) 54 | for tx in txs: 55 | print("id: {:s}".format(tx.hash)) 56 | print( 57 | "height: {:s}".format("None" if tx.height is None else "{:d}".format(tx.height)) 58 | ) 59 | try: 60 | print("size: {:d}".format(tx.size)) 61 | except exceptions.TransactionWithoutBlob: 62 | print("no blob, cannot check size") 63 | print("JSON:") 64 | print(json.dumps(tx.json, indent=2)) 65 | print("-" * 79) 66 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os 3 | import re 4 | from distutils.core import setup 5 | 6 | from setuptools import find_packages 7 | 8 | here = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | 11 | def find_version(*parts): 12 | """ 13 | Figure out version number without importing the package. 14 | https://packaging.python.org/guides/single-sourcing-package-version/ 15 | """ 16 | with codecs.open(os.path.join(here, *parts), "r", errors="ignore") as fp: 17 | version_file = fp.read() 18 | version_match = re.search(r"^__version__ = ['\"](.*)['\"]", version_file, re.M) 19 | if version_match: 20 | return version_match.group(1) 21 | raise RuntimeError("Unable to find version string.") 22 | 23 | 24 | version = find_version("monero", "__init__.py") 25 | 26 | setup( 27 | name="monero", 28 | version=version, 29 | description="A comprehensive Python module for handling Monero cryptocurrency", 30 | url="https://github.com/monero-ecosystem/monero-python/", 31 | long_description=open("README.rst", "rb").read().decode("utf-8"), 32 | install_requires=open("requirements.txt", "r").read().splitlines(), 33 | tests_require=open("test_requirements.txt", "r").read().splitlines(), 34 | packages=find_packages(".", exclude=["tests"]), 35 | include_package_data=True, 36 | author="Michał Sałaban", 37 | author_email="michal@salaban.info", 38 | license="BSD-3-Clause", 39 | classifiers=[ 40 | "Development Status :: 4 - Beta", 41 | "Intended Audience :: Developers", 42 | "License :: OSI Approved :: BSD License", 43 | "Operating System :: OS Independent", 44 | "Programming Language :: Python", 45 | "Programming Language :: Python :: 3.6", 46 | "Programming Language :: Python :: 3.7", 47 | "Programming Language :: Python :: 3.8", 48 | "Programming Language :: Python :: 3.8", 49 | "Programming Language :: Python :: 3.9", 50 | "Programming Language :: Python :: 3.10", 51 | "Topic :: Software Development :: Libraries :: Python Modules", 52 | ], 53 | keywords="monero cryptocurrency", 54 | test_suite="tests", 55 | ) 56 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_from_self__issue_71-1a75f-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj", 7 | "amount": 0, 8 | "confirmations": 10, 9 | "destinations": [ 10 | { 11 | "address": "79QBvrVQGvYdaQauHUBBdxJqGapkMzoQJP31sLXmMYZuHjTJFEariB9gQ2dgYvVtfK8FdJ7covNFJNjSx9PDMPQNFUpBeVU", 12 | "amount": 1000000000000 13 | } 14 | ], 15 | "double_spend_seen": false, 16 | "fee": 94240000, 17 | "height": 689015, 18 | "locked": false, 19 | "note": "", 20 | "payment_id": "0000000000000000", 21 | "subaddr_index": { 22 | "major": 0, 23 | "minor": 0 24 | }, 25 | "subaddr_indices": [ 26 | { 27 | "major": 0, 28 | "minor": 145 29 | } 30 | ], 31 | "timestamp": 1603019911, 32 | "txid": "1a75f3aa57f7912313e90ab1188b7a102dbb619a324c3db51bb856a2f40503f1", 33 | "type": "out", 34 | "unlock_time": 0 35 | }, 36 | "transfers": [ 37 | { 38 | "address": "56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj", 39 | "amount": 0, 40 | "confirmations": 10, 41 | "destinations": [ 42 | { 43 | "address": "79QBvrVQGvYdaQauHUBBdxJqGapkMzoQJP31sLXmMYZuHjTJFEariB9gQ2dgYvVtfK8FdJ7covNFJNjSx9PDMPQNFUpBeVU", 44 | "amount": 1000000000000 45 | } 46 | ], 47 | "double_spend_seen": false, 48 | "fee": 94240000, 49 | "height": 689015, 50 | "locked": false, 51 | "note": "", 52 | "payment_id": "0000000000000000", 53 | "subaddr_index": { 54 | "major": 0, 55 | "minor": 0 56 | }, 57 | "subaddr_indices": [ 58 | { 59 | "major": 0, 60 | "minor": 145 61 | } 62 | ], 63 | "timestamp": 1603019911, 64 | "txid": "1a75f3aa57f7912313e90ab1188b7a102dbb619a324c3db51bb856a2f40503f1", 65 | "type": "out", 66 | "unlock_time": 0 67 | } 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/test_numbers.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | import unittest 3 | 4 | from monero.numbers import to_atomic, from_atomic, as_monero, PaymentID 5 | 6 | 7 | class NumbersTestCase(unittest.TestCase): 8 | def test_simple_numbers(self): 9 | self.assertEqual(to_atomic(Decimal("0")), 0) 10 | self.assertEqual(from_atomic(0), Decimal("0")) 11 | self.assertEqual(to_atomic(Decimal("1")), 1000000000000) 12 | self.assertEqual(from_atomic(1000000000000), Decimal("1")) 13 | self.assertEqual(to_atomic(Decimal("0.000000000001")), 1) 14 | self.assertEqual(from_atomic(1), Decimal("0.000000000001")) 15 | 16 | def test_numeric_types(self): 17 | "Only check if conversion of given type succeeds or fails." 18 | self.assertTrue(to_atomic(1)) 19 | self.assertTrue(to_atomic(1.0)) 20 | self.assertRaises(ValueError, to_atomic, "1") 21 | 22 | def test_rounding(self): 23 | self.assertEqual(to_atomic(Decimal("1.0000000000004")), 1000000000000) 24 | self.assertEqual( 25 | as_monero(Decimal("1.0000000000014")), Decimal("1.000000000001") 26 | ) 27 | 28 | def test_payment_id(self): 29 | pid = PaymentID("0") 30 | self.assertTrue(pid.is_short()) 31 | self.assertEqual(pid, 0) 32 | self.assertEqual(pid, "0000000000000000") 33 | self.assertEqual(PaymentID(pid), pid) 34 | self.assertNotEqual(pid, None) 35 | pid = PaymentID("abcdef") 36 | self.assertTrue(pid.is_short()) 37 | self.assertEqual(pid, 0xABCDEF) 38 | self.assertEqual(pid, "0000000000abcdef") 39 | self.assertEqual(PaymentID(pid), pid) 40 | pid = PaymentID("1234567812345678") 41 | self.assertTrue(pid.is_short()) 42 | self.assertEqual(pid, 0x1234567812345678) 43 | self.assertEqual(pid, "1234567812345678") 44 | self.assertEqual(PaymentID(pid), pid) 45 | pid = PaymentID("a1234567812345678") 46 | self.assertFalse(pid.is_short()) 47 | self.assertEqual(pid, 0xA1234567812345678) 48 | self.assertEqual( 49 | pid, "00000000000000000000000000000000000000000000000a1234567812345678" 50 | ) 51 | self.assertEqual(PaymentID(pid), pid) 52 | self.assertRaises(ValueError, PaymentID, 2**256 + 1) 53 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_outgoing_by_tx_id-afaf0-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 7 | "amount": 40000000000, 8 | "confirmations": 0, 9 | "destinations": [ 10 | { 11 | "address": "72nfCSqpigFWaMeKyZYjKMQRvYFxWvJaf8Nnb1KxVndeTuL7avqoCF5NME4WGMqwmK58i8BnKxCz543mFoZhuUMpGhN1dcm", 12 | "amount": 40000000000 13 | } 14 | ], 15 | "double_spend_seen": false, 16 | "fee": 979320000, 17 | "height": 0, 18 | "note": "", 19 | "payment_id": "0000000000000000", 20 | "subaddr_index": { 21 | "major": 1, 22 | "minor": 0 23 | }, 24 | "subaddr_indices": [ 25 | { 26 | "major": 1, 27 | "minor": 0 28 | } 29 | ], 30 | "suggested_confirmations_threshold": 1, 31 | "timestamp": 1568413596, 32 | "txid": "afaf04e5e40c6b60fc7cc928a88843fc96031ec2b567c310ee61abf3d00020da", 33 | "type": "pending", 34 | "unlock_time": 0 35 | }, 36 | "transfers": [ 37 | { 38 | "address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 39 | "amount": 40000000000, 40 | "confirmations": 0, 41 | "destinations": [ 42 | { 43 | "address": "72nfCSqpigFWaMeKyZYjKMQRvYFxWvJaf8Nnb1KxVndeTuL7avqoCF5NME4WGMqwmK58i8BnKxCz543mFoZhuUMpGhN1dcm", 44 | "amount": 40000000000 45 | } 46 | ], 47 | "double_spend_seen": false, 48 | "fee": 979320000, 49 | "height": 0, 50 | "note": "", 51 | "payment_id": "0000000000000000", 52 | "subaddr_index": { 53 | "major": 1, 54 | "minor": 0 55 | }, 56 | "subaddr_indices": [ 57 | { 58 | "major": 1, 59 | "minor": 0 60 | } 61 | ], 62 | "suggested_confirmations_threshold": 1, 63 | "timestamp": 1568413596, 64 | "txid": "afaf04e5e40c6b60fc7cc928a88843fc96031ec2b567c310ee61abf3d00020da", 65 | "type": "pending", 66 | "unlock_time": 0 67 | } 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_outgoing_by_tx_id-eda89-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 7 | "amount": 21200000000, 8 | "confirmations": 23, 9 | "destinations": [ 10 | { 11 | "address": "72quvHYJ8QzivQyV4NMZ9h1gyZXyJZvsWZTwgVFRCw9b1eJ4yibHEnU3CVCCXJ7evqXhSEKJL2rjzCMV3LpXirR5B8EnkaE", 12 | "amount": 21200000000 13 | } 14 | ], 15 | "double_spend_seen": false, 16 | "fee": 196220000, 17 | "height": 409449, 18 | "note": "", 19 | "payment_id": "0000000000000000", 20 | "subaddr_index": { 21 | "major": 1, 22 | "minor": 0 23 | }, 24 | "subaddr_indices": [ 25 | { 26 | "major": 1, 27 | "minor": 2 28 | } 29 | ], 30 | "suggested_confirmations_threshold": 1, 31 | "timestamp": 1568408151, 32 | "txid": "eda891adf76993f9066abd56a8a5aa5c51a7618298cab59ec37739f1c960596d", 33 | "type": "out", 34 | "unlock_time": 0 35 | }, 36 | "transfers": [ 37 | { 38 | "address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 39 | "amount": 21200000000, 40 | "confirmations": 23, 41 | "destinations": [ 42 | { 43 | "address": "72quvHYJ8QzivQyV4NMZ9h1gyZXyJZvsWZTwgVFRCw9b1eJ4yibHEnU3CVCCXJ7evqXhSEKJL2rjzCMV3LpXirR5B8EnkaE", 44 | "amount": 21200000000 45 | } 46 | ], 47 | "double_spend_seen": false, 48 | "fee": 196220000, 49 | "height": 409449, 50 | "note": "", 51 | "payment_id": "0000000000000000", 52 | "subaddr_index": { 53 | "major": 1, 54 | "minor": 0 55 | }, 56 | "subaddr_indices": [ 57 | { 58 | "major": 1, 59 | "minor": 2 60 | } 61 | ], 62 | "suggested_confirmations_threshold": 1, 63 | "timestamp": 1568408151, 64 | "txid": "eda891adf76993f9066abd56a8a5aa5c51a7618298cab59ec37739f1c960596d", 65 | "type": "out", 66 | "unlock_time": 0 67 | } 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docs/source/backends.rst: -------------------------------------------------------------------------------- 1 | Backends 2 | ======== 3 | 4 | The module comes with possibility of replacing the underlying backend. Backends are the protocols 5 | and methods used to communicate with the Monero daemon or wallet. As of the time of this writing, 6 | the module offers the following options: 7 | 8 | * ``jsonrpc`` for the HTTP based RPC server, 9 | * ``offline`` for running the wallet without Internet connection and even without the wallet file. 10 | 11 | JSON RPC 12 | ---------------- 13 | 14 | This backend requires a running ``monero-wallet-rpc`` process with a Monero wallet file opened. 15 | This can be on your local system or a remote node, depending on where the wallet file lives and 16 | where the daemon is running. Refer to the quickstart for general setup information. 17 | 18 | The Python `requests`_ library is used in order to facilitate HTTP requests to the JSON RPC 19 | interface. It makes POST requests and passes proper headers, parameters, and payload data as per 20 | the official `Wallet RPC`_ documentation. 21 | 22 | Also, ``jsonrpc`` backend is the default choice and both ``Wallet`` and ``Daemon`` classes 23 | can be invoked in a simple form with no ``backend`` argument given. They will assume connection to 24 | the default *mainnet* port on *localhost*, like below: 25 | 26 | .. code-block:: python 27 | 28 | In [1]: wallet = Wallet() # is equivalent to: wallet = Wallet(JSONRPCWallet(host='localhost', port=18081) 29 | 30 | .. _`requests`: http://docs.python-requests.org/ 31 | 32 | .. _`Wallet RPC`: https://getmonero.org/resources/developer-guides/wallet-rpc.html 33 | 34 | .. automodule:: monero.backends.jsonrpc 35 | :members: 36 | 37 | Offline 38 | ---------------- 39 | 40 | This backend allows creating a `Wallet` instance without network connection or even without the 41 | wallet itself. In version 0.5 the only practical use is to cold-generate 42 | :doc:`subaddresses
` like in the example below: 43 | 44 | .. code-block:: python 45 | 46 | In [8]: w = Wallet(OfflineWallet('47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef', view_key='6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901')) 47 | 48 | In [9]: w.get_address(100,37847) 49 | Out[9]: 883Gcsq65iqh4UL3fJTWLxY45skXyFVNQJZ4bdw4TJcqd8vafvtpX4p6HNmawqFMQ6TwJP7adzyLT1fbU6z1n9dqB9bJrfn 50 | 51 | .. automodule:: monero.backends.offline 52 | :members: 53 | -------------------------------------------------------------------------------- /docs/source/outputs.rst: -------------------------------------------------------------------------------- 1 | Output recognition 2 | ================== 3 | 4 | The module provides means to obtain output information from transactions as well as recognize and 5 | decrypt those destined to user's own wallet. 6 | 7 | That functionality is a part of ``Transaction.outputs(wallet=None)`` method which may take a wallet 8 | as optional keyword, which will make it analyze outputs against all wallet's addresses. 9 | The wallet **must have the secret view key** while secret spend key is not required (which means 10 | a view-only wallet is enough). 11 | 12 | .. note:: Be aware that ed25519 cryptography used there is written in pure Python. Don't expect 13 | high efficiency there. If you plan a massive analysis of transactions, please check if 14 | using Monero source code wouldn't be better for you. 15 | 16 | .. note:: Please make sure the wallet you provide has all existing subaddresses generated. 17 | If you run another copy of the wallet and use subaddresses, the wallet you pass to 18 | ``.outputs()`` **must have the same or bigger set of subaddressses present**. For those 19 | missing from the wallet, no recognition will happen. 20 | 21 | Output data 22 | ----------- 23 | 24 | The method will return a set of ``Output`` objects. Each of them contains the following attributes: 25 | 26 | * ``stealth_address`` — the stealth address of the output as hexadecimal string, 27 | * ``amount`` — the amount of the output, ``None`` if unknown, 28 | * ``index`` — the index of the output, 29 | * ``transaction`` — the ``Transaction`` the output is a part of, 30 | * ``payment`` — a ``Payment`` object if the output is destined to provided wallet, 31 | otherwise ``None``, 32 | 33 | An example usage: 34 | 35 | .. code-block:: python 36 | 37 | In [1]: from monero.daemon import Daemon 38 | 39 | In [2]: from monero.wallet import Wallet 40 | 41 | In [3]: daemon = Daemon(port=28081) 42 | 43 | In [4]: tx = daemon.transactions("f79a10256859058b3961254a35a97a3d4d5d40e080c6275a3f9779acde73ca8d")[0] 44 | 45 | In [5]: wallet = Wallet(port=28088) 46 | 47 | In [6]: outs = tx.outputs(wallet=wallet) 48 | 49 | In [7]: outs[0].payment.local_address 50 | Out [7]: 76Qt2xMZ3m7b2tagubEgkvG81pwf9P3JYdxR65H2BEv8c79A9pCBTacEFv87tfdcqXRemBsZLFVGHTWbqBpkoBJENBoJJS9 51 | 52 | In [8]: outs[0].payment.amount 53 | Out [8]: Decimal('4.000000000000') 54 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output-wallet-02-addresses-account-0.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "address": "54LUsTyVL2haFdvkUVngGCiacaRYkjrUvfhvnF6JS2fXNL6twQUQf7PEPtf9MvRYXvhVmtzcV2MUefinDjjwVcH56xm3AHx", 6 | "addresses": [ 7 | { 8 | "address": "54LUsTyVL2haFdvkUVngGCiacaRYkjrUvfhvnF6JS2fXNL6twQUQf7PEPtf9MvRYXvhVmtzcV2MUefinDjjwVcH56xm3AHx", 9 | "address_index": 0, 10 | "label": "Primary account", 11 | "used": true 12 | }, 13 | { 14 | "address": "74fu2Uiveqg8XwxtLK4kdMPkCZNuW8gGC7nWgB8kJF1rACfbGvPKirrSaKFnxtUJePfnGTPRQ1Tp3jR2PKAavqQ7RNrsJbb", 15 | "address_index": 1, 16 | "label": "(Untitled address)", 17 | "used": true 18 | }, 19 | { 20 | "address": "72KSFPEUK5MDvo716tgRrndsRgB3UtWczAXVBjqDZCvRitvUKKA8saNH9LCD1ExCM5VAKuZJTtnH495vEupxRn7EEYbUrGV", 21 | "address_index": 2, 22 | "label": "(Untitled address)", 23 | "used": true 24 | }, 25 | { 26 | "address": "7As2Hf3RwGWSCJAPFFHXDyLg5qywN8MiqcmpWPiM3Mc7bmuoA1V78HWEf3GctXtveHSH35hk6NVGK7TAQoyXpy3ATvEYgPz", 27 | "address_index": 3, 28 | "label": "(Untitled address)", 29 | "used": true 30 | }, 31 | { 32 | "address": "771dkwjPR1UDTMM7NVbJ6LDX2pQdBaHqDZKSzViPTDaxVjG7idfF9uP2i8xftcV1JfZYESDzvFvAUJ1ym89uPy7VQ9Nd9BA", 33 | "address_index": 4, 34 | "label": "(Untitled address)", 35 | "used": true 36 | }, 37 | { 38 | "address": "798wHkYNUqc5UWYdeYiFmrYeXV7Rd23MqasV3YWAYLbeieYb3ogLEkB9TugZkP72qMMtnhxAuyr62P9GLk3Ai5EMS6mCTdu", 39 | "address_index": 5, 40 | "label": "(Untitled address)", 41 | "used": true 42 | }, 43 | { 44 | "address": "7AVgqHzSLu227tv2HGvPAYcbVqTzbbNsAC99QpPZwZzudGsEypD4ZjrQQcansdaQGDLtUbdEhqHfeNjgEWPFAUoVGq1dnbE", 45 | "address_index": 6, 46 | "label": "(Untitled address)", 47 | "used": true 48 | }, 49 | { 50 | "address": "77xBMPG3k5qEmQmreBbeqJYwcx9kkEz2y2abCFvRRrjKZVr2LVois3kGRM4kzXUqmj7PtTkiUza6rXnNZ5HM17pd2AbsVEA", 51 | "address_index": 7, 52 | "label": "(Untitled address)", 53 | "used": true 54 | }, 55 | { 56 | "address": "72barfe7Sp9JZkyLCYLn3wfGhysUUQJqgJPp7DtjStjFdFUXR376ypzYKyxgMcXNE3AStjFmaSKAq6pv78jKPsbTTLq3uNb", 57 | "address_index": 8, 58 | "label": "(Untitled address)", 59 | "used": true 60 | } 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_address_balance-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 12982128625328954, 9 | "base_address": "55LTR8KniP4LQGJSPtbYDacR7dz8RBFnsfAKMaMuwUNYX6aQbBcovzDPyrQF9KXF9tVU6Xk3K8no1BywnJX6GvZX8yJsXvt", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 12982128625328954 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 12000000000000, 17 | "base_address": "77Vx9cs1VPicFndSVgYUvTdLCJEZw9h81hXLMYsjBCXSJfUehLa9TDW3Ffh45SQa7xb6dUs18mpNxfUhQGqfwXPSMrvKhVp", 18 | "label": "Untitled account", 19 | "tag": "", 20 | "unlocked_balance": 12000000000000 21 | }, 22 | { 23 | "account_index": 2, 24 | "balance": 11222000000000, 25 | "base_address": "78P16M3XmFRGcWFCcsgt1WcTntA1jzcq31seQX1Eg92j8VQ99NPivmdKam4J5CKNAD7KuNWcq5xUPgoWczChzdba5WLwQ4j", 26 | "label": "Untitled account", 27 | "tag": "", 28 | "unlocked_balance": 11222000000000 29 | }, 30 | { 31 | "account_index": 3, 32 | "balance": 2000000000000, 33 | "base_address": "76FWbfaWr3igfJhf2B5zBMjCWKZpaiGHbfE7vqgDGDSGWs2mfTBNHqTTMAqkYsxg6XEojV4Pax8wV9Fqvu1Ghy7nQiSzcXQ", 34 | "label": "Untitled account", 35 | "tag": "", 36 | "unlocked_balance": 2000000000000 37 | }, 38 | { 39 | "account_index": 4, 40 | "balance": 1000000000000, 41 | "base_address": "77ETXXSc6PkAF7WkJPFoL43ivyoBq9kpC38GbCa7Z6T72VpWXWBKpK6QQgL47VF196h7EFExy43knMYscAyCYz9F44nEDd5", 42 | "label": "Untitled account", 43 | "tag": "", 44 | "unlocked_balance": 1000000000000 45 | }, 46 | { 47 | "account_index": 5, 48 | "balance": 200000000000000, 49 | "base_address": "73mFYjLVDF9HSaNfAnGpqTBvNaCyBDGqx9NAPaKBhCYWRMxUERWDkk6hgzh4dEuwKTPy3UMdLL91zF5BxrwzBtQNAKfqf5W", 50 | "label": "Untitled account", 51 | "tag": "", 52 | "unlocked_balance": 200000000000000 53 | }, 54 | { 55 | "account_index": 6, 56 | "balance": 50000000000, 57 | "base_address": "78jehncLRUCKiNHQ1DBqwDBREs32X9Ec1ebU2EFM2cDLah6qDxM4d1RQ9uRymEa71r4qKPxY2KpVv9ueMJEbAHL7RELQqqA", 58 | "label": "Untitled account", 59 | "tag": "", 60 | "unlocked_balance": 50000000000 61 | } 62 | ], 63 | "total_balance": 13208400625328954, 64 | "total_unlocked_balance": 13208400625328954 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /utils/pushtx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import argparse 3 | import logging 4 | import operator 5 | import re 6 | import sys 7 | 8 | from monero.backends.jsonrpc import JSONRPCDaemon 9 | from monero.daemon import Daemon 10 | from monero.transaction import Transaction 11 | from monero import exceptions 12 | 13 | 14 | def url_data(url): 15 | gs = re.compile(r"^(?P[^:\s]+)(?::(?P[0-9]+))?$").match(url).groupdict() 16 | return dict(filter(operator.itemgetter(1), gs.items())) 17 | 18 | 19 | argsparser = argparse.ArgumentParser(description="Push transaction to network") 20 | argsparser.add_argument( 21 | "daemon_rpc_url", 22 | nargs="?", 23 | type=url_data, 24 | default="127.0.0.1:18081", 25 | help="Daemon RPC URL [host[:port]]", 26 | ) 27 | argsparser.add_argument( 28 | "-v", 29 | dest="verbosity", 30 | action="count", 31 | default=0, 32 | help="Verbosity (repeat to increase; -v for INFO, -vv for DEBUG", 33 | ) 34 | argsparser.add_argument( 35 | "-p", dest="proxy_url", nargs="?", type=str, default=None, help="Proxy URL" 36 | ) 37 | argsparser.add_argument( 38 | "-t", dest="timeout", type=int, default=30, help="Request timeout" 39 | ) 40 | argsparser.add_argument( 41 | "-i", 42 | dest="tx_filenames", 43 | nargs="+", 44 | default=None, 45 | help="Files with transaction data. Will read from stdin if not given.", 46 | ) 47 | argsparser.add_argument( 48 | "--no-relay", 49 | dest="relay", 50 | action="store_false", 51 | help="Do not relay the transaction (it will stay at the node unless mined or expired)", 52 | ) 53 | args = argsparser.parse_args() 54 | level = logging.WARNING 55 | if args.verbosity == 1: 56 | level = logging.INFO 57 | elif args.verbosity > 1: 58 | level = logging.DEBUG 59 | logging.basicConfig(level=level, format="%(asctime)-15s %(message)s") 60 | if args.tx_filenames: 61 | blobs = [(f, open(f, "rb").read()) for f in args.tx_filenames] 62 | else: 63 | blobs = [("transaction", sys.stdin.buffer.read())] 64 | d = Daemon( 65 | JSONRPCDaemon(timeout=args.timeout, proxy_url=args.proxy_url, **args.daemon_rpc_url) 66 | ) 67 | for name, blob in blobs: 68 | logging.debug("Sending {}".format(name)) 69 | tx = Transaction(blob=blob) 70 | try: 71 | res = d.send_transaction(tx, relay=args.relay) 72 | except exceptions.TransactionBroadcastError as e: 73 | print("{} not sent, reason: {}".format(name, e.details["reason"])) 74 | logging.debug(e.details) 75 | continue 76 | if res["not_relayed"]: 77 | print("{} not relayed".format(name)) 78 | else: 79 | print("{} successfully sent".format(name)) 80 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_outgoing_by_tx_id-362c3-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 7 | "amount": 520000000000, 8 | "confirmations": 23, 9 | "destinations": [ 10 | { 11 | "address": "77yjHxBeLNq4mf4dhEB7ksD6WDaAEEguqHHvuFYyiLiMWwSrvYHftzT5c1HRS9iWa2UBn4MQLuz8jEiE6sPDfMzB81UMHaK", 12 | "amount": 220000000000 13 | }, 14 | { 15 | "address": "787CXWuevtt2SdD9x6rB7hCk73pYVv7HYgAUAPsYQJ9zAmQN7Ksxr5KieQFXuEL6ZSMqMDNbbaUze365iF2DbkjB9bcL82t", 16 | "amount": 300000000000 17 | } 18 | ], 19 | "double_spend_seen": false, 20 | "fee": 280650000, 21 | "height": 409449, 22 | "note": "", 23 | "payment_id": "0000000000000000", 24 | "subaddr_index": { 25 | "major": 1, 26 | "minor": 0 27 | }, 28 | "subaddr_indices": [ 29 | { 30 | "major": 1, 31 | "minor": 1 32 | } 33 | ], 34 | "suggested_confirmations_threshold": 1, 35 | "timestamp": 1568408151, 36 | "txid": "362c3a4e601d5847b3882c3debfd28a0ee31654e433c38498539677199c304c2", 37 | "type": "out", 38 | "unlock_time": 0 39 | }, 40 | "transfers": [ 41 | { 42 | "address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 43 | "amount": 520000000000, 44 | "confirmations": 23, 45 | "destinations": [ 46 | { 47 | "address": "77yjHxBeLNq4mf4dhEB7ksD6WDaAEEguqHHvuFYyiLiMWwSrvYHftzT5c1HRS9iWa2UBn4MQLuz8jEiE6sPDfMzB81UMHaK", 48 | "amount": 220000000000 49 | }, 50 | { 51 | "address": "787CXWuevtt2SdD9x6rB7hCk73pYVv7HYgAUAPsYQJ9zAmQN7Ksxr5KieQFXuEL6ZSMqMDNbbaUze365iF2DbkjB9bcL82t", 52 | "amount": 300000000000 53 | } 54 | ], 55 | "double_spend_seen": false, 56 | "fee": 280650000, 57 | "height": 409449, 58 | "note": "", 59 | "payment_id": "0000000000000000", 60 | "subaddr_index": { 61 | "major": 1, 62 | "minor": 0 63 | }, 64 | "subaddr_indices": [ 65 | { 66 | "major": 1, 67 | "minor": 1 68 | } 69 | ], 70 | "suggested_confirmations_threshold": 1, 71 | "timestamp": 1568408151, 72 | "txid": "362c3a4e601d5847b3882c3debfd28a0ee31654e433c38498539677199c304c2", 73 | "type": "out", 74 | "unlock_time": 0 75 | } 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_master-wallet-02-addresses-account-0.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "address": "54LUsTyVL2haFdvkUVngGCiacaRYkjrUvfhvnF6JS2fXNL6twQUQf7PEPtf9MvRYXvhVmtzcV2MUefinDjjwVcH56xm3AHx", 6 | "addresses": [ 7 | { 8 | "address": "54LUsTyVL2haFdvkUVngGCiacaRYkjrUvfhvnF6JS2fXNL6twQUQf7PEPtf9MvRYXvhVmtzcV2MUefinDjjwVcH56xm3AHx", 9 | "address_index": 0, 10 | "label": "Primary account", 11 | "used": true 12 | }, 13 | { 14 | "address": "74fu2Uiveqg8XwxtLK4kdMPkCZNuW8gGC7nWgB8kJF1rACfbGvPKirrSaKFnxtUJePfnGTPRQ1Tp3jR2PKAavqQ7RNrsJbb", 15 | "address_index": 1, 16 | "label": "(Untitled address)", 17 | "used": true 18 | }, 19 | { 20 | "address": "72KSFPEUK5MDvo716tgRrndsRgB3UtWczAXVBjqDZCvRitvUKKA8saNH9LCD1ExCM5VAKuZJTtnH495vEupxRn7EEYbUrGV", 21 | "address_index": 2, 22 | "label": "(Untitled address)", 23 | "used": true 24 | }, 25 | { 26 | "address": "7As2Hf3RwGWSCJAPFFHXDyLg5qywN8MiqcmpWPiM3Mc7bmuoA1V78HWEf3GctXtveHSH35hk6NVGK7TAQoyXpy3ATvEYgPz", 27 | "address_index": 3, 28 | "label": "(Untitled address)", 29 | "used": true 30 | }, 31 | { 32 | "address": "771dkwjPR1UDTMM7NVbJ6LDX2pQdBaHqDZKSzViPTDaxVjG7idfF9uP2i8xftcV1JfZYESDzvFvAUJ1ym89uPy7VQ9Nd9BA", 33 | "address_index": 4, 34 | "label": "(Untitled address)", 35 | "used": true 36 | }, 37 | { 38 | "address": "798wHkYNUqc5UWYdeYiFmrYeXV7Rd23MqasV3YWAYLbeieYb3ogLEkB9TugZkP72qMMtnhxAuyr62P9GLk3Ai5EMS6mCTdu", 39 | "address_index": 5, 40 | "label": "(Untitled address)", 41 | "used": true 42 | }, 43 | { 44 | "address": "7AVgqHzSLu227tv2HGvPAYcbVqTzbbNsAC99QpPZwZzudGsEypD4ZjrQQcansdaQGDLtUbdEhqHfeNjgEWPFAUoVGq1dnbE", 45 | "address_index": 6, 46 | "label": "(Untitled address)", 47 | "used": true 48 | }, 49 | { 50 | "address": "77xBMPG3k5qEmQmreBbeqJYwcx9kkEz2y2abCFvRRrjKZVr2LVois3kGRM4kzXUqmj7PtTkiUza6rXnNZ5HM17pd2AbsVEA", 51 | "address_index": 7, 52 | "label": "(Untitled address)", 53 | "used": true 54 | }, 55 | { 56 | "address": "72barfe7Sp9JZkyLCYLn3wfGhysUUQJqgJPp7DtjStjFdFUXR376ypzYKyxgMcXNE3AStjFmaSKAq6pv78jKPsbTTLq3uNb", 57 | "address_index": 8, 58 | "label": "(Untitled address)", 59 | "used": true 60 | }, 61 | { 62 | "address": "77a1kZkpr9uZMD2kcunkE67JiNN6dZhXAUa6ACFtP8j4MqTipM1QW5FSLkeEJZ5jzaMYNauPcavyPMF2VCVr8i5XEFizNkv", 63 | "address_index": 9, 64 | "label": "(Untitled address)", 65 | "used": false 66 | } 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /monero/numbers.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | PICONERO = Decimal("0.000000000001") 4 | EMPTY_KEY = "0" * 64 5 | 6 | 7 | def to_atomic(amount): 8 | """Convert Monero decimal to atomic integer of piconero.""" 9 | if not isinstance(amount, (Decimal, int, float)): 10 | raise ValueError( 11 | "Amount '{}' doesn't have numeric type. Only Decimal, int, long and " 12 | "float (not recommended) are accepted as amounts.".format(amount) 13 | ) 14 | return int(amount * 10**12) 15 | 16 | 17 | def from_atomic(amount): 18 | """Convert atomic integer of piconero to Monero decimal.""" 19 | return (Decimal(amount) * PICONERO).quantize(PICONERO) 20 | 21 | 22 | def as_monero(amount): 23 | """Return the amount rounded to maximal Monero precision.""" 24 | return Decimal(amount).quantize(PICONERO) 25 | 26 | 27 | class PaymentID(object): 28 | """ 29 | A class that validates Monero payment ID. 30 | 31 | Payment IDs can be used as str or int across the module, however this class 32 | offers validation as well as simple conversion and comparison to those two 33 | primitive types. 34 | 35 | :param payment_id: the payment ID as integer or hexadecimal string 36 | """ 37 | 38 | _payment_id = None 39 | 40 | def __init__(self, payment_id): 41 | if isinstance(payment_id, PaymentID): 42 | payment_id = int(payment_id) 43 | if isinstance(payment_id, str): 44 | payment_id = int(payment_id, 16) 45 | elif not isinstance(payment_id, int): 46 | raise TypeError( 47 | "payment_id must be either int or hexadecimal str or bytes, " 48 | "is {0}".format(type(payment_id)) 49 | ) 50 | if payment_id.bit_length() > 256: 51 | raise ValueError( 52 | "payment_id {0} is more than 256 bits long".format(payment_id) 53 | ) 54 | self._payment_id = payment_id 55 | 56 | def is_short(self): 57 | """Returns True if payment ID is short enough to be included 58 | in :class:`IntegratedAddress `.""" 59 | return self._payment_id.bit_length() <= 64 60 | 61 | def __repr__(self): 62 | if self.is_short(): 63 | return "{:016x}".format(self._payment_id) 64 | return "{:064x}".format(self._payment_id) 65 | 66 | def __int__(self): 67 | return self._payment_id 68 | 69 | def __eq__(self, other): 70 | if isinstance(other, PaymentID): 71 | return int(self) == int(other) 72 | elif isinstance(other, int): 73 | return int(self) == other 74 | elif isinstance(other, str): 75 | return str(self) == other 76 | return super(PaymentID, self).__eq__(other) 77 | -------------------------------------------------------------------------------- /utils/daemoninfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import argparse 3 | import logging 4 | import operator 5 | import re 6 | 7 | from monero.backends.jsonrpc import JSONRPCDaemon 8 | from monero.daemon import Daemon 9 | 10 | 11 | def url_data(url): 12 | gs = ( 13 | re.compile( 14 | r"^(?:(?P[a-z0-9_-]+)?(?::(?P[^@]+))?@)?(?P[^:\s]+)(?::(?P[0-9]+))?$" 15 | ) 16 | .match(url) 17 | .groupdict() 18 | ) 19 | return dict(filter(operator.itemgetter(1), gs.items())) 20 | 21 | 22 | def get_daemon(): 23 | argsparser = argparse.ArgumentParser(description="Display daemon info") 24 | argsparser.add_argument( 25 | "daemon_rpc_url", 26 | nargs="?", 27 | type=url_data, 28 | default="127.0.0.1:18081", 29 | help="Daemon RPC URL [user[:password]@]host[:port]", 30 | ) 31 | argsparser.add_argument( 32 | "-p", dest="proxy_url", nargs="?", type=str, default=None, help="Proxy URL" 33 | ) 34 | argsparser.add_argument( 35 | "-t", dest="timeout", type=int, default=30, help="Request timeout" 36 | ) 37 | argsparser.add_argument( 38 | "-v", 39 | dest="verbosity", 40 | action="count", 41 | default=0, 42 | help="Verbosity (repeat to increase; -v for INFO, -vv for DEBUG", 43 | ) 44 | args = argsparser.parse_args() 45 | level = logging.WARNING 46 | if args.verbosity == 1: 47 | level = logging.INFO 48 | elif args.verbosity > 1: 49 | level = logging.DEBUG 50 | logging.basicConfig(level=level, format="%(asctime)-15s %(message)s") 51 | return Daemon( 52 | JSONRPCDaemon( 53 | timeout=args.timeout, proxy_url=args.proxy_url, **args.daemon_rpc_url 54 | ) 55 | ) 56 | 57 | 58 | d = get_daemon() 59 | info = d.info() 60 | print( 61 | "Net: {net:>20s}net\n" 62 | "Height: {height:15d}\n" 63 | "Difficulty: {difficulty:15d}\n" 64 | "Alt blocks: {alt_blocks_count:15d}\n".format( 65 | net="test" 66 | if info["testnet"] 67 | else "stage" 68 | if info["stagenet"] 69 | else "main" 70 | if info["mainnet"] 71 | else "unknown", 72 | **info 73 | ) 74 | ) 75 | print("Last 6 blocks:") 76 | for hdr in reversed(d.headers(info["height"] - 6, info["height"] - 1)): 77 | print( 78 | "{height:10d} {hash} {block_size_kb:6.2f} kB {num_txes:3d} txn(s) " 79 | "v{major_version:d}".format(block_size_kb=hdr["block_size"] / 1024.0, **hdr) 80 | ) 81 | mempool = d.mempool() 82 | if mempool: 83 | print("\n{:d} txn(s) in mempool:".format(len(mempool))) 84 | for tx in d.mempool(): 85 | print("{:>10s} {:s}".format(tx.timestamp.strftime("%H:%M:%S"), tx.hash)) 86 | else: 87 | print("\nMempool is empty") 88 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_block_template_481SgRxo_64.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "blockhashing_blob": "0e0ec890de8006fbe94dbaf09e5fc4431460ede46c0e7c3db4adf1f0a6f34c5a0f5144e6d9dd270000000067ec6d160094c242a6e4aee48103c5c80f100e8a7055ed5ff7964216f8fc47561c", 6 | "blocktemplate_blob": "0e0ec890de8006fbe94dbaf09e5fc4431460ede46c0e7c3db4adf1f0a6f34c5a0f5144e6d9dd270000000002fecb8b0101ffc2cb8b0101d5fea9bbae220232e3df54fbf87eb29983853b3a4c6e3505949b011459334647ef459d304f6b53630102aeb562f6095dddf3ddd07d7703e047c274639bdaab3df83ac366d471db1d14024000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b313357d05b6bede5aa4797627a17e728f6577438bbd616f763efc06f6f09535a2c0ea18d5cda3019cef79db816e4a7e1b18d49385b44a77fb8e6b4d22762190b6c54d1d099d7d4179601a792f38c0aa14df8e40685215a53f92b9a4b54d2a2c8b69e0ded589859e3f1e310e99a754612c331052aec895950948eadf2a4e98d597f252fba93b2bd7c77b4e2a826a8e0fd6f11e96bfc16e0c7e390affc4a22032064099c83439afcd9f3805deb15407582dccdb2ffd62af8f0ed55a1fa6c4fff4e8e492e36cad4bf5481b57e37137df5c097a26f28c16b9e563d7291feebd9775e207e205f533853fdeacc33da212567dff2ebcb235cb046c0ccc39c59ba6a98b97732ce2a27332a397344a7a40b452d23c999a06497731a3289a7470281f8725e7f3d14f62b05156db38d190c1391e3c3e17e277375c4ce4f5a8bed6d4f0bd472021de649597df2eabf40badaeee8e66e56fdf9d345b4731fed35f8ba601c1373a4dbba49ee4fec380fe3616ca53341f8c9f2aa89e6715ff774bc8e24af78d851a5a88deed42f90de59115db74841b6e90cf6bfb1671cd7ff843b4918c0601dea4dc63d9beca34d6e76c4544c98a9e6c68c3b9cd0a43b371832b4a086755c81d5b415f618f4d69209b99472b4e14d07b05299bf2700de70d7680be47a97922adeda28495ac40d74a0f01d282d85832ccbfb880d6a3031ab13326f784c0ddcc625bfcfba2f95a71450c1c66ed558ecdf131ccf98085831e3e48c4b92f60b4ca7040b977717abe4a3ef1b030fd7c51e9182ec6829f104e5ee8e9f5ec3049634524162798328af418467129946357b26debaefa4b4b29676c199a7b120cb97f91b186c5d9d5c1c68b0ac6c188ded8993f893a9feee37afc5a8113c1de57a07a5df1046e9065b3e0a0f894e18b00d73ea5850998feadf31dc3278bf438350aba7b43f06311b60dfe9948d58012598626fbdb4058764198f6d4e44a4599b62cf826e6ec530f056f44b1cc6f5886472f68a70c2af78d2e2651d427fb292daa3bcbe18c255ebc7e7f82e97d948ced5998c661555bcf72225a8e97dbea7ec85f09f2200034119572300475d4ec4a0ede8ea7a8f6a7362865c34b797967592a3562df6e745bc63c240589509757e217ac7c42e93614dc99bd0462a7cabb9deda505f5ce83c96f0a14cd80e0dd485a1d6671ee0845fa0bc56b3e294975d878f05733505564b", 7 | "difficulty": 232871166515, 8 | "difficulty_top64": 0, 9 | "expected_reward": 1180703555413, 10 | "height": 2287042, 11 | "next_seed_hash": "", 12 | "prev_hash": "fbe94dbaf09e5fc4431460ede46c0e7c3db4adf1f0a6f34c5a0f5144e6d9dd27", 13 | "reserved_offset": 130, 14 | "seed_hash": "d432f499205150873b2572b5f033c9c6e4b7c6f3394bd2dd93822cd7085e7307", 15 | "seed_height": 2285568, 16 | "status": "OK", 17 | "untrusted": false, 18 | "wide_difficulty": "0x3638340233" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /monero/daemon.py: -------------------------------------------------------------------------------- 1 | from .backends.jsonrpc import JSONRPCDaemon 2 | 3 | 4 | class Daemon(object): 5 | """Monero daemon. 6 | 7 | Provides interface to a daemon instance. 8 | 9 | :param backend: a daemon backend 10 | :param \\**kwargs: arguments to initialize a :class:`JSONRPCDaemon ` 11 | instance if no backend is given 12 | """ 13 | 14 | def __init__(self, backend=None, **kwargs): 15 | if backend and len(kwargs): 16 | raise ValueError("backend already given, other arguments are extraneous") 17 | 18 | self._backend = backend if backend else JSONRPCDaemon(**kwargs) 19 | 20 | def info(self): 21 | """ 22 | Returns basic information about the daemon. 23 | 24 | :rtype: dict 25 | """ 26 | return self._backend.info() 27 | 28 | @property 29 | def net(self): 30 | return self._backend.net() 31 | 32 | def height(self): 33 | """ 34 | Return daemon's chain height. 35 | 36 | :rtype: int 37 | """ 38 | return self._backend.info()["height"] 39 | 40 | def send_transaction(self, tx, relay=True): 41 | """ 42 | Sends a transaction generated by a :class:`Wallet `. 43 | 44 | :param tx: :class:`Transaction ` 45 | :param relay: whether to relay the transaction to peers. If `False`, the daemon will have 46 | to mine the transaction itself in order to have it included in the blockchain. 47 | """ 48 | return self._backend.send_transaction(tx.blob, relay=relay) 49 | 50 | def mempool(self): 51 | """ 52 | Returns current mempool contents. 53 | 54 | :rtype: list of :class:`Transaction ` 55 | """ 56 | return self._backend.mempool() 57 | 58 | def headers(self, start_height, end_height=None): 59 | """ 60 | Returns block headers for given height range. 61 | If no :param end_height: is given, it's assumed to be equal to :param start_height: 62 | 63 | :rtype: list of dict 64 | """ 65 | return self._backend.headers(start_height, end_height) 66 | 67 | def block(self, bhash=None, height=None): 68 | """ 69 | Returns a block of specified height or hash. 70 | 71 | :param str bhash: block hash, or 72 | :param int height: block height 73 | 74 | :rtype: :class:`Block ` 75 | """ 76 | if height is None and bhash is None: 77 | raise ValueError("Height or hash must be specified") 78 | return self._backend.block(bhash=bhash, height=height) 79 | 80 | def transactions(self, hashes): 81 | """ 82 | Returns transactions matching given hashes. Accepts single hash or a sequence. 83 | 84 | :param hashes: str or list of str 85 | """ 86 | if isinstance(hashes, str): 87 | hashes = [hashes] 88 | return self._backend.transactions(hashes) 89 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_block-423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "blob": "0b0cd6e0afee0551f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce413af2580802d4cb1b01ff98cb1b01d6a9ddfc9bbe0302e351068aea8f05e9e30f876c3f203a89d09e7e07786979d79df6c7f8cbb75ff12101aaa20f9b81d64636002de35a6a4914ac4fb36145da0ba0f9d670e6c2b6a11c8000047e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de124fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0", 6 | "block_header": { 7 | "block_size": 9632, 8 | "block_weight": 9632, 9 | "cumulative_difficulty": 11915811790, 10 | "cumulative_difficulty_top64": 0, 11 | "depth": 49003, 12 | "difficulty": 3590, 13 | "difficulty_top64": 0, 14 | "hash": "423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89", 15 | "height": 451992, 16 | "long_term_weight": 9632, 17 | "major_version": 11, 18 | "miner_tx_hash": "f2bd4cb3dafd5c096be7e1ec908f98bf34903f5a013faa65a0d0c8998154c583", 19 | "minor_version": 12, 20 | "nonce": 140046906, 21 | "num_txes": 4, 22 | "orphan_status": false, 23 | "pow_hash": "", 24 | "prev_hash": "51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41", 25 | "reward": 15331952645334, 26 | "timestamp": 1573646422, 27 | "wide_cumulative_difficulty": "0x2c63cdbce", 28 | "wide_difficulty": "0xe06" 29 | }, 30 | "credits": 0, 31 | "json": "{\n \"major_version\": 11, \n \"minor_version\": 12, \n \"timestamp\": 1573646422, \n \"prev_id\": \"51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41\", \n \"nonce\": 140046906, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 452052, \n \"vin\": [ {\n \"gen\": {\n \"height\": 451992\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 15331952645334, \n \"target\": {\n \"key\": \"e351068aea8f05e9e30f876c3f203a89d09e7e07786979d79df6c7f8cbb75ff1\"\n }\n }\n ], \n \"extra\": [ 1, 170, 162, 15, 155, 129, 214, 70, 54, 0, 45, 227, 90, 106, 73, 20, 172, 79, 179, 97, 69, 218, 11, 160, 249, 214, 112, 230, 194, 182, 161, 28, 128\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ \"7e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b\", \"3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106\", \"bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de1\", \"24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0\"\n ]\n}", 32 | "miner_tx_hash": "", 33 | "status": "OK", 34 | "top_hash": "", 35 | "tx_hashes": [ 36 | "7e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b", 37 | "3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106", 38 | "bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de1", 39 | "24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0" 40 | ], 41 | "untrusted": false 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /utils/transfer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import argparse 3 | import operator 4 | import logging 5 | import os 6 | import re 7 | 8 | import monero 9 | from monero.address import address 10 | from monero.numbers import as_monero 11 | from monero.wallet import Wallet 12 | from monero.backends.jsonrpc import JSONRPCWallet 13 | 14 | 15 | def url_data(url): 16 | gs = ( 17 | re.compile( 18 | r"^(?:(?P[a-z0-9_-]+)?(?::(?P[^@]+))?@)?(?P[^:\s]+)(?::(?P[0-9]+))?$" 19 | ) 20 | .match(url) 21 | .groupdict() 22 | ) 23 | return dict(filter(operator.itemgetter(1), gs.items())) 24 | 25 | 26 | def destpair(s): 27 | addr, amount = s.split(":") 28 | return (address(addr), as_monero(amount)) 29 | 30 | 31 | argsparser = argparse.ArgumentParser(description="Transfer Monero") 32 | argsparser.add_argument( 33 | "-v", 34 | dest="verbosity", 35 | action="count", 36 | default=0, 37 | help="Verbosity (repeat to increase; -v for INFO, -vv for DEBUG", 38 | ) 39 | argsparser.add_argument( 40 | "wallet_rpc_url", 41 | nargs="?", 42 | type=url_data, 43 | default="127.0.0.1:18082", 44 | help="Wallet RPC URL [user[:password]@]host[:port]", 45 | ) 46 | argsparser.add_argument( 47 | "-t", dest="timeout", type=int, default=30, help="Request timeout" 48 | ) 49 | argsparser.add_argument( 50 | "-a", dest="account", default=0, type=int, help="Source account index" 51 | ) 52 | argsparser.add_argument( 53 | "-p", 54 | dest="prio", 55 | choices=["unimportant", "normal", "elevated", "priority"], 56 | default="normal", 57 | ) 58 | argsparser.add_argument( 59 | "--save", 60 | dest="outdir", 61 | nargs="?", 62 | default=None, 63 | const=".", 64 | help="Save to file, optionally follow by destination directory (default is .)\n" 65 | "Transaction will be not relayed to the network.", 66 | ) 67 | argsparser.add_argument( 68 | "destinations", 69 | metavar="address:amount", 70 | nargs="+", 71 | type=destpair, 72 | help="Destination address and amount (one or more pairs)", 73 | ) 74 | args = argsparser.parse_args() 75 | prio = getattr(monero.const, "PRIO_{:s}".format(args.prio.upper())) 76 | 77 | level = logging.WARNING 78 | if args.verbosity == 1: 79 | level = logging.INFO 80 | elif args.verbosity > 1: 81 | level = logging.DEBUG 82 | logging.basicConfig(level=level, format="%(asctime)-15s %(message)s") 83 | 84 | w = Wallet(JSONRPCWallet(timeout=args.timeout, **args.wallet_rpc_url)) 85 | txns = w.accounts[args.account].transfer_multiple( 86 | args.destinations, priority=prio, relay=args.outdir is None 87 | ) 88 | for tx in txns: 89 | print( 90 | u"Transaction {hash}:\nfee: {fee:21.12f}\n" 91 | u"Tx key: {key}\nSize: {size} B".format( 92 | hash=tx.hash, fee=tx.fee, key=tx.key, size=len(tx.blob) >> 1 93 | ) 94 | ) 95 | if args.outdir: 96 | outname = os.path.join(args.outdir, tx.hash + ".tx") 97 | outfile = open(outname, "wb") 98 | outfile.write(tx.blob) 99 | outfile.close() 100 | print(u"Transaction saved to {}".format(outname)) 101 | -------------------------------------------------------------------------------- /tests/test_block.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import unittest 3 | 4 | from monero.block import Block 5 | from monero.numbers import from_atomic 6 | from monero.transaction import Transaction 7 | 8 | 9 | class BlockTestCase(unittest.TestCase): 10 | def setUp(self): 11 | self.tx1 = Transaction( 12 | hash="7e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b" 13 | ) 14 | self.tx2 = Transaction( 15 | hash="3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106" 16 | ) 17 | self.tx3 = Transaction( 18 | hash="bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de1" 19 | ) 20 | self.tx4 = Transaction( 21 | hash="24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0" 22 | ) 23 | self.block1 = Block( 24 | hash="423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89", 25 | height=451992, 26 | difficulty=3590, 27 | version=(11, 12), 28 | nonce=140046906, 29 | orphan=False, 30 | prev_hash="51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41", 31 | reward=from_atomic(15331952645334), 32 | timestamp=datetime.fromtimestamp(1573646422), 33 | transactions=[self.tx1, self.tx2, self.tx3, self.tx4], 34 | ) 35 | self.block1_duplicate = Block( 36 | hash="423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89", 37 | height=451992, 38 | difficulty=3590, 39 | version=(11, 12), 40 | nonce=140046906, 41 | orphan=False, 42 | prev_hash="51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41", 43 | reward=from_atomic(15331952645334), 44 | timestamp=datetime.fromtimestamp(1573646422), 45 | transactions=[self.tx1, self.tx2, self.tx3, self.tx4], 46 | ) 47 | 48 | def test_basic_ops(self): 49 | self.assertIsNot(self.block1, self.block1_duplicate) 50 | self.assertEqual(self.block1, self.block1_duplicate) 51 | self.assertEqual(self.block1, self.block1.hash) 52 | self.assertEqual(self.block1, self.block1.hash) 53 | self.assertNotEqual(self.block1, 1) 54 | 55 | def test_tx_membership(self): 56 | self.assertIn(self.tx1, self.block1) 57 | self.assertIn(self.tx2, self.block1) 58 | self.assertIn(self.tx3, self.block1) 59 | self.assertIn(self.tx4, self.block1) 60 | self.assertIn(self.tx1, self.block1_duplicate) 61 | self.assertIn(self.tx2, self.block1_duplicate) 62 | self.assertIn(self.tx3, self.block1_duplicate) 63 | self.assertIn(self.tx4, self.block1_duplicate) 64 | 65 | def test_tx_hash_membership(self): 66 | self.assertIn(self.tx1.hash, self.block1) 67 | self.assertIn(self.tx2.hash, self.block1) 68 | self.assertIn(self.tx3.hash, self.block1) 69 | self.assertIn(self.tx4.hash, self.block1) 70 | self.assertIn(self.tx1.hash, self.block1_duplicate) 71 | self.assertIn(self.tx2.hash, self.block1_duplicate) 72 | self.assertIn(self.tx3.hash, self.block1_duplicate) 73 | self.assertIn(self.tx4.hash, self.block1_duplicate) 74 | -------------------------------------------------------------------------------- /docs/source/wallet.rst: -------------------------------------------------------------------------------- 1 | Using wallet and accounts 2 | ========================= 3 | 4 | The Wallet class provides an abstraction layer to retrieve wallet information, manage accounts and 5 | subaddresses, and of course send transfers. 6 | 7 | The wallet 8 | ---------- 9 | 10 | The following example shows how to create and retrieve wallet's accounts and 11 | addresses via the default JSON RPC backend: 12 | 13 | .. code-block:: python 14 | 15 | In [1]: from monero.wallet import Wallet 16 | 17 | In [2]: w = Wallet(port=28088) 18 | 19 | In [3]: w.address() 20 | Out[3]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX 21 | 22 | Accounts and subaddresses 23 | ------------------------- 24 | 25 | The accounts are stored in wallet's ``accounts`` attribute, which is a list. 26 | 27 | Regardless of the version, **the wallet by default operates on its account of 28 | index 0**, which makes it consistent with the behavior of the CLI wallet 29 | client. 30 | 31 | .. code-block:: python 32 | 33 | In [4]: len(w.accounts) 34 | Out[4]: 1 35 | 36 | In [5]: w.accounts[0] 37 | Out[5]: 38 | 39 | In [6]: w.accounts[0].address() 40 | Out[6]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX 41 | 42 | In [7]: w.addresses() 43 | Out[7]: [A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX] 44 | 45 | 46 | Creating accounts and addresses 47 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 48 | 49 | Every wallet can have separate accounts and each account can have numerous 50 | addresses. The ``Wallet.new_account()`` and ``Account.new_address()`` will 51 | create new instances, then return a tuple consisting of the subaddress itself, 52 | and the subaddress index within the account. 53 | 54 | .. code-block:: python 55 | 56 | In [8]: w.new_address() 57 | Out[8]: (BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7, 1) 58 | 59 | In [9]: w.addresses() 60 | Out[9]: 61 | [A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX, 62 | BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7] 63 | 64 | In [10]: w.new_account() 65 | Out[10]: 66 | 67 | In [11]: len(w.accounts) 68 | Out[11]: 2 69 | 70 | In [12]: w.accounts[1].address() 71 | Out[12]: Bhd3PRVCnq5T5jjNey2hDSM8DxUgFpNjLUrKAa2iYVhYX71RuCGTekDKZKXoJPAGL763kEXaDSAsvDYb8bV77YT7Jo19GKY 72 | 73 | In [13]: w.accounts[1].new_address() 74 | Out[13]: (Bbz5uCtnn3Gaj1YAizaHw1FPeJ6T7kk7uQoeY48SWjezEAyrWScozLxYbqGxsV5L6VJkvw5VwECAuLVJKQtHpA3GFXJNPYu, 1) 75 | 76 | In [14]: w.accounts[1].addresses() 77 | Out[14]: 78 | [Bhd3PRVCnq5T5jjNey2hDSM8DxUgFpNjLUrKAa2iYVhYX71RuCGTekDKZKXoJPAGL763kEXaDSAsvDYb8bV77YT7Jo19GKY, 79 | Bbz5uCtnn3Gaj1YAizaHw1FPeJ6T7kk7uQoeY48SWjezEAyrWScozLxYbqGxsV5L6VJkvw5VwECAuLVJKQtHpA3GFXJNPYu] 80 | 81 | 82 | As mentioned above, the wallet by default operates on the first account, so 83 | ``w.new_address()`` is equivalent to ``w.accounts[0].new_address()``. 84 | 85 | In the next chapter we will :doc:`learn about addresses
`. 86 | 87 | API reference 88 | ------------- 89 | 90 | .. automodule:: monero.wallet 91 | :members: 92 | 93 | .. automodule:: monero.account 94 | :members: 95 | -------------------------------------------------------------------------------- /monero/transaction/extra.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import varint 3 | 4 | 5 | class ExtraParser(object): 6 | TX_EXTRA_TAG_PADDING = 0x00 7 | TX_EXTRA_TAG_PUBKEY = 0x01 8 | TX_EXTRA_TAG_EXTRA_NONCE = 0x02 9 | TX_EXTRA_TAG_ADDITIONAL_PUBKEYS = 0x04 10 | KNOWN_TAGS = ( 11 | TX_EXTRA_TAG_PADDING, 12 | TX_EXTRA_TAG_PUBKEY, 13 | TX_EXTRA_TAG_EXTRA_NONCE, 14 | TX_EXTRA_TAG_ADDITIONAL_PUBKEYS, 15 | ) 16 | 17 | def __init__(self, extra): 18 | if isinstance(extra, str): 19 | extra = binascii.unhexlify(extra) 20 | if isinstance(extra, bytes): 21 | extra = list(extra) 22 | self.extra = extra 23 | 24 | def parse(self): 25 | self.data = {} 26 | self.offset = 0 27 | self._parse(self.extra) 28 | return self.data 29 | 30 | def _strip_padding(self, extra): 31 | while extra and extra[0] == self.TX_EXTRA_TAG_PADDING: 32 | extra = extra[1:] 33 | self.offset += 1 34 | return extra 35 | 36 | def _pop_pubkey(self, extra): 37 | key = bytes(bytearray(extra[:32])) # bytearray() is for py2 compatibility 38 | if len(key) < 32: 39 | raise ValueError( 40 | "offset {:d}: only {:d} bytes of key data, expected 32".format( 41 | self.offset, len(key) 42 | ) 43 | ) 44 | if "pubkeys" in self.data: 45 | self.data["pubkeys"].append(key) 46 | else: 47 | self.data["pubkeys"] = [key] 48 | extra = extra[32:] 49 | self.offset += 32 50 | return extra 51 | 52 | def _extract_pubkey(self, extra): 53 | if extra: 54 | if extra[0] == self.TX_EXTRA_TAG_PUBKEY: 55 | extra = extra[1:] 56 | self.offset += 1 57 | extra = self._pop_pubkey(extra) 58 | elif extra[0] == self.TX_EXTRA_TAG_ADDITIONAL_PUBKEYS: 59 | extra = extra[1:] 60 | self.offset += 1 61 | keycount = varint.decode_bytes(bytearray(extra)) 62 | valen = len(varint.encode(keycount)) 63 | extra = extra[valen:] 64 | self.offset += valen 65 | for i in range(keycount): 66 | extra = self._pop_pubkey(extra) 67 | return extra 68 | 69 | def _extract_nonce(self, extra): 70 | if extra and extra[0] == self.TX_EXTRA_TAG_EXTRA_NONCE: 71 | noncelen = extra[1] 72 | extra = extra[2:] 73 | self.offset += 2 74 | if noncelen > len(extra): 75 | raise ValueError( 76 | "offset {:d}: extra nonce exceeds field size".format(self.offset) 77 | ) 78 | nonce = bytearray(extra[:noncelen]) 79 | if "nonces" in self.data: 80 | self.data["nonces"].append(nonce) 81 | else: 82 | self.data["nonces"] = [nonce] 83 | extra = extra[noncelen:] 84 | self.offset += noncelen 85 | return extra 86 | 87 | def _parse(self, extra): 88 | while extra: 89 | if extra[0] not in self.KNOWN_TAGS: 90 | raise ValueError( 91 | "offset {:d}: unknown tag 0x{:x}".format(self.offset, extra[0]) 92 | ) 93 | extra = self._strip_padding(extra) 94 | extra = self._extract_pubkey(extra) 95 | extra = self._extract_nonce(extra) 96 | -------------------------------------------------------------------------------- /docs/source/release_notes.rst: -------------------------------------------------------------------------------- 1 | Release Notes 2 | ============= 3 | 4 | 1.1.0 5 | ----- 6 | 7 | This version doesn't contain any major changes but drops support for Python 2 altogether. 8 | 9 | 1.0.2 10 | ----- 11 | 12 | **A release with critical security fix.** All since 0.99 (inclusively) are compromised and should 13 | be never used again. 14 | 15 | 1.0 16 | --- 17 | 18 | A release with no significant changes from 0.99 19 | 20 | 0.99 21 | ---- 22 | 23 | This is a test release before 1.0. The reference library for Ed25519 cryptography has been dropped 24 | and replaced with `pynacl`_ which is a wrapper over `libsodium`_, the industry standard 25 | lightning-fast C library. 26 | 27 | There are no backward-incompatible changes in the API. The aim is to have the software tested 28 | thoroughly before the first stable release. 29 | 30 | .. _`pynacl`: https://github.com/pyca/pynacl/ 31 | .. _`libsodium`: https://github.com/jedisct1/libsodium/ 32 | 33 | 0.9 34 | --- 35 | 36 | The hashing library ``sha3`` has been replaced by ``pycryptodomex`` which is a more actively 37 | maintained project. However, the code still may work with the old ``sha3`` module. Just ignore 38 | the new dependency and run as usual. 39 | 40 | 0.8 41 | --- 42 | 43 | Backward-incompatible changes: 44 | 45 | 1. The ``monero.prio`` submodule has been removed. Switch to ``monero.const``. 46 | 2. Methods ``.is_mainnet()``, ``.is_testnet()``, ``.is_stagenet()`` have been removed from 47 | ``monero.address.Address`` instances. Use ``.net`` attribute instead. 48 | 49 | 0.7 50 | --- 51 | 52 | Backward-incompatible changes: 53 | 54 | 1. The ``Transaction.blob`` changes from hexadecimal to raw binary data (``bytes`` in Python 3, 55 | ``str`` in Python 2). 56 | 57 | Deprecations: 58 | 59 | 1. ``monero.const`` has been introduced. Transaction priority consts will move to 60 | ``monero.const.PRIO_*``. The ``monero.prio`` submodule has been deprecated and will be gone 61 | in 0.8. 62 | 2. Methods ``.is_mainnet()``, ``.is_testnet()``, ``.is_stagenet()`` have been deprecated and 63 | new ``.net`` property has been added to all ``monero.address.Address`` instances. The values 64 | are from among ``monero.const.NET_*`` and have string representation of ``"main"``, ``"test"`` 65 | and ``"stage"`` respectively. Likewise, ``monero.seed.Seed.public_address()`` accepts those 66 | new values. 67 | All deprecated uses will raise proper warnings in 0.7.x and will be gone with 0.8. 68 | 69 | 0.6 70 | --- 71 | 72 | With version 0.6 the package name on PyPi has changed from `monero-python` to just `monero`. 73 | 74 | Backward-incompatible changes: 75 | 76 | 1. The ``.new_address()`` method of both ``Wallet`` and ``Account`` returns a 2-element tuple of 77 | (`subaddress`, `index`) where the additional element is the index of the subaddress within 78 | current account. 79 | 80 | 0.5 81 | --- 82 | 83 | Backward-incompatible changes: 84 | 85 | 1. The ``ringsize`` parameter is gone from ``.transfer()`` and ``.transfer_multiple()`` methods of 86 | both ``Wallet`` and ``Account``. Since Monero 0.13 the ring size is of constant value 11. 87 | 2. The class hierarchy in ``monero.address`` has been reordered. ``Address`` now represents only 88 | master address of a wallet. ``SubAddress`` doesn't inherit after it anymore, but all classes 89 | share the common base of ``BaseAddress``. 90 | 91 | In particular, make sure that your code doesn't check a presence of Monero address by checking 92 | ``isinstance(x, monero.address.Address)``. That will not work for sub-addresses anymore. 93 | Replace it by ``isinstance(x, monero.address.BaseAddress)``. 94 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_incoming_by_tx_id-7ab84-get_transfer_by_txid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "transfer": { 6 | "address": "76SJ4sPWzgQKE3fBbAoRTC7HtewGAo37VEgMpmHPEfPMRssYQgdeVyfJt5rcEcHw9dJJ4cLSkZ9c5fPTnKFHKh43UKmJs25", 7 | "amount": 1000000000000, 8 | "confirmations": 208, 9 | "double_spend_seen": false, 10 | "fee": 292510000, 11 | "height": 409227, 12 | "note": "", 13 | "payment_id": "0000000000000000", 14 | "subaddr_index": { 15 | "major": 0, 16 | "minor": 8 17 | }, 18 | "subaddr_indices": [ 19 | { 20 | "major": 0, 21 | "minor": 8 22 | } 23 | ], 24 | "suggested_confirmations_threshold": 1, 25 | "timestamp": 1568388430, 26 | "txid": "7ab84fe2fb34467c590cde2f7d6ba7de5928a2db6c84c6ccfff8962eca0ad99c", 27 | "type": "in", 28 | "unlock_time": 0 29 | }, 30 | "transfers": [ 31 | { 32 | "address": "76SJ4sPWzgQKE3fBbAoRTC7HtewGAo37VEgMpmHPEfPMRssYQgdeVyfJt5rcEcHw9dJJ4cLSkZ9c5fPTnKFHKh43UKmJs25", 33 | "amount": 1000000000000, 34 | "confirmations": 208, 35 | "double_spend_seen": false, 36 | "fee": 292510000, 37 | "height": 409227, 38 | "note": "", 39 | "payment_id": "0000000000000000", 40 | "subaddr_index": { 41 | "major": 0, 42 | "minor": 8 43 | }, 44 | "subaddr_indices": [ 45 | { 46 | "major": 0, 47 | "minor": 8 48 | } 49 | ], 50 | "suggested_confirmations_threshold": 1, 51 | "timestamp": 1568388430, 52 | "txid": "7ab84fe2fb34467c590cde2f7d6ba7de5928a2db6c84c6ccfff8962eca0ad99c", 53 | "type": "in", 54 | "unlock_time": 0 55 | }, 56 | { 57 | "address": "75LwnK3zfQS5mEzxgEdyep8SSwnvGSKcMLnCpzUqCF4CMFHDNrSnCojfoTRV9EWy2Y3ejeFLMPH9tAjagMAim8F8EKWjHos", 58 | "amount": 1000000000000, 59 | "confirmations": 208, 60 | "double_spend_seen": false, 61 | "fee": 292510000, 62 | "height": 409227, 63 | "note": "", 64 | "payment_id": "0000000000000000", 65 | "subaddr_index": { 66 | "major": 0, 67 | "minor": 19 68 | }, 69 | "subaddr_indices": [ 70 | { 71 | "major": 0, 72 | "minor": 19 73 | } 74 | ], 75 | "suggested_confirmations_threshold": 1, 76 | "timestamp": 1568388430, 77 | "txid": "7ab84fe2fb34467c590cde2f7d6ba7de5928a2db6c84c6ccfff8962eca0ad99c", 78 | "type": "in", 79 | "unlock_time": 0 80 | }, 81 | { 82 | "address": "74sZRQ2sHs4YLh8PnW8fseUoUoM4bXQ3wQ6bfCr6YyxmK5QRawKLytF9CfRbuv581LEnXBj27Dwg6eNC4fhhrH9kUvpbWQ5", 83 | "amount": 2000000000000, 84 | "confirmations": 208, 85 | "double_spend_seen": false, 86 | "fee": 292510000, 87 | "height": 409227, 88 | "note": "", 89 | "payment_id": "0000000000000000", 90 | "subaddr_index": { 91 | "major": 0, 92 | "minor": 29 93 | }, 94 | "subaddr_indices": [ 95 | { 96 | "major": 0, 97 | "minor": 29 98 | } 99 | ], 100 | "suggested_confirmations_threshold": 1, 101 | "timestamp": 1568388430, 102 | "txid": "7ab84fe2fb34467c590cde2f7d6ba7de5928a2db6c84c6ccfff8962eca0ad99c", 103 | "type": "in", 104 | "unlock_time": 0 105 | } 106 | ] 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quick start 2 | =========== 3 | 4 | This quick start tutorial will guide you through the first steps of connecting 5 | to the Monero wallet. We assume you: 6 | 7 | * have basic knowledge of Monero concepts of the wallet and daemon, 8 | * know how to use CLI (*command line interface*), 9 | * have experience with Python. 10 | 11 | Connect to testnet for your own safety 12 | -------------------------------------- 13 | 14 | The testnet is another Monero network where worthless coins circulate and 15 | where, as the name suggests, all tests are supposed to be run. It's also a 16 | place for early deployment of future features of the currency itself. You may 17 | read `a brief explanation at stackexchange`_. 18 | 19 | .. warning:: **Please run all tests on testnet.** The code presented in these docs will 20 | perform the requested operations right away, without asking for confirmation. 21 | This is live code, not a wallet application that makes sure the user has not 22 | made a mistake. **Running on the live net, if you make a mistake, you may lose 23 | money.** 24 | 25 | .. _a brief explanation at stackexchange: https://monero.stackexchange.com/questions/1591/what-is-the-monero-testnet-how-can-i-participate-in-it 26 | 27 | Start the daemon and create a wallet 28 | ------------------------------------ 29 | 30 | In order to connect to the testnet network you need to start the daemon: 31 | 32 | .. code-block:: shell 33 | 34 | $ monerod --testnet 35 | 36 | 37 | If you haven't used testnet before, it will begin downloading the blockchain, 38 | exactly like it does on the live network. In January 2018 the testnet 39 | blockchain was slightly over 2 GiB. It may take some time to get it. 40 | 41 | You may however create a wallet in the meantime: 42 | 43 | .. code-block:: shell 44 | 45 | $ monero-wallet-cli --testnet --generate-new-wallet testwallet 46 | 47 | For now you may leave the password empty (testnet coins are worthless). 48 | 49 | Start the RPC server 50 | -------------------- 51 | 52 | The RPC server is a small utility that will operate on the wallet, exposing 53 | a JSON RPC interface. Start it by typing: 54 | 55 | .. code-block:: shell 56 | 57 | $ monero-wallet-rpc --testnet --wallet-file testwallet --password "" --rpc-bind-port 28088 --disable-rpc-login 58 | 59 | Now you're almost ready to start using Python. 60 | 61 | Install Dependencies 62 | --------------------- 63 | 64 | Before you can use the library, you first must download the Python library dependencies with ``pip``. It is recommended to use a `virtual environment`_ to isolate library versions. Assuming you have ``virtualenv`` installed to your system, set up a new env, activate it, and install the dependencies. 65 | 66 | .. _`virtual environment`: https://averlytics.com/2017/08/06/virtual-environment-a-python-best-practice/ 67 | 68 | .. code-block:: shell 69 | 70 | $ virtualenv .venv 71 | $ source .venv/bin/activate 72 | $ pip install -r requirements.txt 73 | $ python 74 | 75 | Now you can proceed. 76 | 77 | Connect to the wallet 78 | --------------------- 79 | 80 | .. code-block:: python 81 | 82 | In [1]: from monero.wallet import Wallet 83 | 84 | In [2]: from monero.backends.jsonrpc import JSONRPCWallet 85 | 86 | In [3]: w = Wallet(JSONRPCWallet(port=28088)) 87 | 88 | In [4]: w.address() 89 | Out[4]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX 90 | 91 | In [5]: w.balance() 92 | Out[5]: Decimal('0E-12') 93 | 94 | Congratulations! You have connected to the wallet. You may now proceed to the 95 | next part, which will tell you about :doc:`interaction with wallet and accounts `. 96 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output-daemon-00-get_transactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "status": "OK", 4 | "top_hash": "", 5 | "txs": [ 6 | { 7 | "as_hex": "", 8 | "as_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 2313951, 2369591, 6872, 24959, 92810, 7131, 3340, 1143, 339, 738, 2551\n ], \n \"k_image\": \"303a40bcd7ebf070c0fca85e1511dfc0ebfcc22896c26ddb716d10305ac958de\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"a420b2c58787c178692deaf57fac6378c66961aa1a3d8e836e372ecf2f962f4a\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"0a000a61af77f57d98b2f5fe8b60916fa352a42d6452e6f944fbbe6afa1932ce\"\n }\n }\n ], \n \"extra\": [ 1, 37, 69, 31, 72, 139, 82, 83, 161, 38, 66, 216, 33, 84, 215, 192, 153, 130, 249, 83, 23, 127, 226, 126, 131, 247, 185, 246, 215, 166, 166, 22, 243, 2, 9, 1, 219, 16, 60, 245, 234, 28, 46, 40\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 44980000, \n \"ecdhInfo\": [ {\n \"amount\": \"fd3411b593b40183\"\n }, {\n \"amount\": \"36ced8b3bd8cb576\"\n }], \n \"outPk\": [ \"5f8ee14c061d617aaa5194afd3b9edf16f6fe75991783d20d89681ffd6f4fc9d\", \"1263698276ab4091d4bfb5ec978b8ce67c3702f9bf3c7e511ebb1340e4d407a4\"]\n }\n}", 9 | "block_height": 1077880, 10 | "block_timestamp": 1650746744, 11 | "double_spend_seen": false, 12 | "in_pool": false, 13 | "output_indices": [ 14 | 4823652, 15 | 4823653 16 | ], 17 | "prunable_as_hex": "", 18 | "prunable_hash": "58abeb3b8e06ce3320cf503ef15a762258bbcabf0d2bbdc3eb4636b7d9001bcc", 19 | "pruned_as_hex": "02000102000bdf9d8d01b7d09001d835ffc2018ad505db378c1af708d302e205f713303a40bcd7ebf070c0fca85e1511dfc0ebfcc22896c26ddb716d10305ac958de020002a420b2c58787c178692deaf57fac6378c66961aa1a3d8e836e372ecf2f962f4a00020a000a61af77f57d98b2f5fe8b60916fa352a42d6452e6f944fbbe6afa1932ce2c0125451f488b5253a12642d82154d7c09982f953177fe27e83f7b9f6d7a6a616f3020901db103cf5ea1c2e2805a0aeb915fd3411b593b4018336ced8b3bd8cb5765f8ee14c061d617aaa5194afd3b9edf16f6fe75991783d20d89681ffd6f4fc9d1263698276ab4091d4bfb5ec978b8ce67c3702f9bf3c7e511ebb1340e4d407a4", 20 | "tx_hash": "f5aff33df23c1410217f852a3740d1af89a44bdd0b95107e54e161f202f16d3c" 21 | } 22 | ], 23 | "txs_as_hex": [ 24 | "" 25 | ], 26 | "txs_as_json": [ 27 | "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 2313951, 2369591, 6872, 24959, 92810, 7131, 3340, 1143, 339, 738, 2551\n ], \n \"k_image\": \"303a40bcd7ebf070c0fca85e1511dfc0ebfcc22896c26ddb716d10305ac958de\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"a420b2c58787c178692deaf57fac6378c66961aa1a3d8e836e372ecf2f962f4a\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"0a000a61af77f57d98b2f5fe8b60916fa352a42d6452e6f944fbbe6afa1932ce\"\n }\n }\n ], \n \"extra\": [ 1, 37, 69, 31, 72, 139, 82, 83, 161, 38, 66, 216, 33, 84, 215, 192, 153, 130, 249, 83, 23, 127, 226, 126, 131, 247, 185, 246, 215, 166, 166, 22, 243, 2, 9, 1, 219, 16, 60, 245, 234, 28, 46, 40\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 44980000, \n \"ecdhInfo\": [ {\n \"amount\": \"fd3411b593b40183\"\n }, {\n \"amount\": \"36ced8b3bd8cb576\"\n }], \n \"outPk\": [ \"5f8ee14c061d617aaa5194afd3b9edf16f6fe75991783d20d89681ffd6f4fc9d\", \"1263698276ab4091d4bfb5ec978b8ce67c3702f9bf3c7e511ebb1340e4d407a4\"]\n }\n}" 28 | ], 29 | "untrusted": false 30 | } 31 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_master-daemon-00-get_transactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "status": "OK", 4 | "top_hash": "", 5 | "txs": [ 6 | { 7 | "as_hex": "", 8 | "as_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 4325102, 464564, 19546, 7828, 15534, 180, 1645, 763, 454, 2046, 9\n ], \n \"k_image\": \"235fa321338a6317a6064fc13b9917c5b9e36dc929b0d3f01d19a6ff9a1d9169\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"4cd1c255c3247d2c2324fb580dd66f1158acc0ba352a4bddf9bb5d724f2b9d4a\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"72a976004da61eabbe7ef1f074ac1ad960347f18b63e79c0b4075c70c6faa71a\"\n }\n }\n ], \n \"extra\": [ 1, 147, 181, 21, 141, 147, 122, 16, 161, 230, 64, 52, 218, 173, 173, 125, 69, 35, 14, 168, 251, 151, 140, 33, 64, 2, 68, 15, 75, 31, 24, 59, 150, 2, 9, 1, 56, 181, 21, 158, 240, 73, 6, 247\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 44760000, \n \"ecdhInfo\": [ {\n \"amount\": \"bb400d3177c30ab5\"\n }, {\n \"amount\": \"c284adb69f34912c\"\n }], \n \"outPk\": [ \"2b63045a7fc922e73eb74cf179ca1640f36ba97ce8f27137dc30ca8e407d99c1\", \"a25db81bcfaf13950d9a353c6c2a55f45e103fa91d3dadad19cb1087f425fa96\"]\n }\n}", 9 | "block_height": 1079424, 10 | "block_timestamp": 1650936396, 11 | "double_spend_seen": false, 12 | "in_pool": false, 13 | "output_indices": [ 14 | 4837766, 15 | 4837767 16 | ], 17 | "prunable_as_hex": "", 18 | "prunable_hash": "ab173d3c08a7251cfd9ca58c67345368828f6f5f1d92972b79d080489956c154", 19 | "pruned_as_hex": "02000102000beefd8702b4ad1cda9801943dae79b401ed0cfb05c603fe0f09235fa321338a6317a6064fc13b9917c5b9e36dc929b0d3f01d19a6ff9a1d91690200024cd1c255c3247d2c2324fb580dd66f1158acc0ba352a4bddf9bb5d724f2b9d4a000272a976004da61eabbe7ef1f074ac1ad960347f18b63e79c0b4075c70c6faa71a2c0193b5158d937a10a1e64034daadad7d45230ea8fb978c214002440f4b1f183b9602090138b5159ef04906f705c0f7ab15bb400d3177c30ab5c284adb69f34912c2b63045a7fc922e73eb74cf179ca1640f36ba97ce8f27137dc30ca8e407d99c1a25db81bcfaf13950d9a353c6c2a55f45e103fa91d3dadad19cb1087f425fa96", 20 | "tx_hash": "f7e60d07c8e201b779871e3817edf8388e6ea775922def5758aef231d3ced36a" 21 | } 22 | ], 23 | "txs_as_hex": [ 24 | "" 25 | ], 26 | "txs_as_json": [ 27 | "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 4325102, 464564, 19546, 7828, 15534, 180, 1645, 763, 454, 2046, 9\n ], \n \"k_image\": \"235fa321338a6317a6064fc13b9917c5b9e36dc929b0d3f01d19a6ff9a1d9169\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"4cd1c255c3247d2c2324fb580dd66f1158acc0ba352a4bddf9bb5d724f2b9d4a\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"72a976004da61eabbe7ef1f074ac1ad960347f18b63e79c0b4075c70c6faa71a\"\n }\n }\n ], \n \"extra\": [ 1, 147, 181, 21, 141, 147, 122, 16, 161, 230, 64, 52, 218, 173, 173, 125, 69, 35, 14, 168, 251, 151, 140, 33, 64, 2, 68, 15, 75, 31, 24, 59, 150, 2, 9, 1, 56, 181, 21, 158, 240, 73, 6, 247\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 44760000, \n \"ecdhInfo\": [ {\n \"amount\": \"bb400d3177c30ab5\"\n }, {\n \"amount\": \"c284adb69f34912c\"\n }], \n \"outPk\": [ \"2b63045a7fc922e73eb74cf179ca1640f36ba97ce8f27137dc30ca8e407d99c1\", \"a25db81bcfaf13950d9a353c6c2a55f45e103fa91d3dadad19cb1087f425fa96\"]\n }\n}" 28 | ], 29 | "untrusted": false 30 | } 31 | -------------------------------------------------------------------------------- /tests/data/test_outputs/test_v2_single_output_with_fake_amount_to_subaddr-daemon-00-get_transactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "status": "OK", 4 | "top_hash": "", 5 | "txs": [ 6 | { 7 | "as_hex": "", 8 | "as_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 4675357, 101625, 27510, 10864, 17256, 2990, 745, 369, 17, 654, 290\n ], \n \"k_image\": \"8d07ba16fd1bcc8eeb2fa485eb293baafe4a06f6096f948fedb6679606c7ea9c\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"5c09c39a189d1ceb7bceb1929a4fc676642d4277fdd3abaec2a3f7c0368e8077\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"1a8b5aa09c1ccfb310ad913a6b577a7e723a53619fb4fd1d9b5e831fc1694be0\"\n }\n }\n ], \n \"extra\": [ 1, 65, 88, 164, 63, 169, 150, 146, 177, 47, 124, 96, 195, 68, 227, 158, 193, 234, 47, 182, 8, 160, 78, 94, 188, 2, 231, 225, 89, 49, 186, 237, 123, 2, 9, 1, 130, 171, 128, 151, 207, 6, 115, 252\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 44790000, \n \"ecdhInfo\": [ {\n \"amount\": \"1ad451cc41a9baf7\"\n }, {\n \"amount\": \"0a6ded7a1104d184\"\n }], \n \"outPk\": [ \"9338cbbdc552ac770797da8af3a589b02e9ad5928d439875760d89dfae1de4ba\", \"05e5685aa757e6420ef889107c7d4c0fc64c698f8bbaa7192ba7164c88ce8b46\"]\n }\n}", 9 | "block_height": 1079424, 10 | "block_timestamp": 1650936396, 11 | "double_spend_seen": false, 12 | "in_pool": false, 13 | "output_indices": [ 14 | 4837768, 15 | 4837769 16 | ], 17 | "prunable_as_hex": "", 18 | "prunable_hash": "7c4eccf0c4f947280645e3aa18f250b90d8466bdedc9371bcca780321fa1e00b", 19 | "pruned_as_hex": "02000102000b9dae9d02f99906f6d601f054e88601ae17e905f102118e05a2028d07ba16fd1bcc8eeb2fa485eb293baafe4a06f6096f948fedb6679606c7ea9c0200025c09c39a189d1ceb7bceb1929a4fc676642d4277fdd3abaec2a3f7c0368e807700021a8b5aa09c1ccfb310ad913a6b577a7e723a53619fb4fd1d9b5e831fc1694be02c014158a43fa99692b12f7c60c344e39ec1ea2fb608a04e5ebc02e7e15931baed7b02090182ab8097cf0673fc05f0e1ad151ad451cc41a9baf70a6ded7a1104d1849338cbbdc552ac770797da8af3a589b02e9ad5928d439875760d89dfae1de4ba05e5685aa757e6420ef889107c7d4c0fc64c698f8bbaa7192ba7164c88ce8b46", 20 | "tx_hash": "54731f9263e92c9e249d8eb677f7c8f1c7edb9812092479f026062139842e0e0" 21 | } 22 | ], 23 | "txs_as_hex": [ 24 | "" 25 | ], 26 | "txs_as_json": [ 27 | "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 4675357, 101625, 27510, 10864, 17256, 2990, 745, 369, 17, 654, 290\n ], \n \"k_image\": \"8d07ba16fd1bcc8eeb2fa485eb293baafe4a06f6096f948fedb6679606c7ea9c\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"5c09c39a189d1ceb7bceb1929a4fc676642d4277fdd3abaec2a3f7c0368e8077\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"1a8b5aa09c1ccfb310ad913a6b577a7e723a53619fb4fd1d9b5e831fc1694be0\"\n }\n }\n ], \n \"extra\": [ 1, 65, 88, 164, 63, 169, 150, 146, 177, 47, 124, 96, 195, 68, 227, 158, 193, 234, 47, 182, 8, 160, 78, 94, 188, 2, 231, 225, 89, 49, 186, 237, 123, 2, 9, 1, 130, 171, 128, 151, 207, 6, 115, 252\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 44790000, \n \"ecdhInfo\": [ {\n \"amount\": \"1ad451cc41a9baf7\"\n }, {\n \"amount\": \"0a6ded7a1104d184\"\n }], \n \"outPk\": [ \"9338cbbdc552ac770797da8af3a589b02e9ad5928d439875760d89dfae1de4ba\", \"05e5685aa757e6420ef889107c7d4c0fc64c698f8bbaa7192ba7164c88ce8b46\"]\n }\n}" 28 | ], 29 | "untrusted": false 30 | } 31 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python Monero module 2 | ==================== 3 | 4 | |travis|_ |coveralls|_ 5 | 6 | 7 | .. |travis| image:: https://travis-ci.org/monero-ecosystem/monero-python.svg 8 | .. _travis: https://travis-ci.org/monero-ecosystem/monero-python 9 | 10 | 11 | .. |coveralls| image:: https://coveralls.io/repos/github/monero-ecosystem/monero-python/badge.svg 12 | .. _coveralls: https://coveralls.io/github/monero-ecosystem/monero-python 13 | 14 | .. warning:: **URGENT SECURITY UPDATE** 15 | The version 1.0.2 contains an urgent security update in the output recognition code. If you're 16 | using the module for scanning transactions and identifying outputs using the secret view key, 17 | UPDATE THE SOFTWARE IMMEDIATELY. 18 | Otherwise you're safe. Standard wallet operations like receiving payments, spending, address 19 | generation etc. are NOT AFFECTED. 20 | 21 | A comprehensive Python module for handling Monero cryptocurrency. 22 | 23 | * release 1.1.1 24 | * open source: https://github.com/monero-ecosystem/monero-python 25 | * works with Monero 0.18.x and `the latest source`_ (at least we try to keep up) 26 | * available on PyPi: https://pypi.org/project/monero/ 27 | * comes with `documentation`_ 28 | * generously funded by `Monero FFS`_ donors 29 | 30 | .. warning:: With release 0.6 the project name at PyPi has changed from `monero-python` to `monero`. 31 | Please update your dependency files. 32 | 33 | .. _`the latest source`: https://github.com/monero-project/monero 34 | .. _`documentation`: http://monero-python.readthedocs.io/en/latest/ 35 | .. _`Monero FFS`: https://forum.getmonero.org/9/work-in-progress 36 | 37 | Copyrights 38 | ---------- 39 | 40 | Released under the BSD 3-Clause License. See `LICENSE.txt`_. 41 | 42 | Copyright (c) 2017-2018 Michał Sałaban and Contributors: 43 | `lalanza808`_, `cryptochangements34`_, `atward`_, `rooterkyberian`_, `brucexiu`_, 44 | `lialsoftlab`_, `moneroexamples`_, `massanchik`_, `MrClottom`_, `jeffro256`_, 45 | `sometato`_, `kayabaNerve`_, `j-berman`_. 46 | 47 | Copyright (c) 2016 The MoneroPy Developers (``monero/base58.py`` taken from `MoneroPy`_) 48 | 49 | Copyright (c) 2011 thomasv@gitorious (``monero/seed.py`` based on `Electrum`_) 50 | 51 | .. _`LICENSE.txt`: LICENSE.txt 52 | .. _`MoneroPy`: https://github.com/bigreddmachine/MoneroPy 53 | .. _`Electrum`: https://github.com/spesmilo/electrum 54 | 55 | .. _`lalanza808`: https://github.com/lalanza808 56 | .. _`cryptochangements34`: https://github.com/cryptochangements34 57 | .. _`atward`: https://github.com/atward 58 | .. _`rooterkyberian`: https://github.com/rooterkyberian 59 | .. _`brucexiu`: https://github.com/brucexiu 60 | .. _`lialsoftlab`: https://github.com/lialsoftlab 61 | .. _`moneroexamples`: https://github.com/moneroexamples 62 | .. _`massanchik`: https://github.com/massanchik 63 | .. _`MrClottom`: https://github.com/MrClottom 64 | .. _`jeffro256`: https://github.com/jeffro256 65 | .. _`sometato`: https://github.com/sometato 66 | .. _`kayabaNerve`: https://github.com/kayabaNerve 67 | .. _`j-berman`: https://github.com/j-berman 68 | 69 | Want to help? 70 | ------------- 71 | 72 | If you find this project useful, please consider a donation to the following address: 73 | ``8AWCa5moRywJcmA6jqnKLJWZMUyoEAFJXBqqDonUnR1SjS8foScqTadcXyE6oVb6Mh2JFeWZtocGxZoZZsMqLNKbNm5Wt3q`` 74 | 75 | 76 | Development 77 | ----------- 78 | 79 | 1. Clone the repo 80 | 2. Create virtualenv & activate it 81 | 82 | .. code-block:: bash 83 | 84 | python3 -m venv .venv 85 | source .venv/bin/activate 86 | 87 | 3. Install dependencies 88 | 89 | .. code-block:: bash 90 | 91 | .venv/bin/pip install -r requirements.txt -r test_requirements.txt 92 | 93 | 4. Do your thing 94 | 95 | 5. Run tests 96 | 97 | .. code-block:: bash 98 | 99 | .venv/bin/pytest 100 | 101 | 6. Format your code with black 102 | 103 | .. code-block:: bash 104 | 105 | .venv/bin/black . 106 | -------------------------------------------------------------------------------- /monero/wordlists/wordlist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | from binascii import crc32 6 | 7 | 8 | WORDLISTS = {} 9 | _log = logging.getLogger(__name__) 10 | 11 | 12 | class WordlistType(type): 13 | def __new__(cls, name, bases, attrs): 14 | if bases: 15 | if "language_name" not in attrs: 16 | raise TypeError("Missing language_name for {0}".format(name)) 17 | if "unique_prefix_length" not in attrs: 18 | raise TypeError("Missing 'unique_prefix_length' for {0}".format(name)) 19 | if "word_list" not in attrs: 20 | raise TypeError("Missing 'word_list' for {0}".format(name)) 21 | 22 | if "english_language_name" not in attrs: 23 | _log.warn( 24 | "No 'english_language_name' for {0} using '{1}'".format( 25 | name, language_name 26 | ) 27 | ) 28 | attrs["english_language_name"] = attrs["language_name"] 29 | 30 | if len(attrs["word_list"]) != 1626: 31 | raise TypeError("Wrong word list length for {0}".format(name)) 32 | 33 | new_cls = super(WordlistType, cls).__new__(cls, name, bases, attrs) 34 | 35 | if bases: 36 | WORDLISTS[new_cls.english_language_name] = new_cls 37 | 38 | return new_cls 39 | 40 | 41 | class Wordlist(metaclass=WordlistType): 42 | n = 1626 43 | 44 | @classmethod 45 | def encode(cls, hex): 46 | """Convert hexadecimal string to mnemonic word representation with checksum.""" 47 | out = [] 48 | for i in range(len(hex) // 8): 49 | word = endian_swap(hex[8 * i : 8 * i + 8]) 50 | x = int(word, 16) 51 | w1 = x % cls.n 52 | w2 = (x // cls.n + w1) % cls.n 53 | w3 = (x // cls.n // cls.n + w2) % cls.n 54 | out += [cls.word_list[w1], cls.word_list[w2], cls.word_list[w3]] 55 | checksum = cls.get_checksum(" ".join(out)) 56 | out.append(checksum) 57 | return " ".join(out) 58 | 59 | @classmethod 60 | def decode(cls, phrase): 61 | """Calculate hexadecimal representation of the phrase.""" 62 | phrase = phrase.split(" ") 63 | out = "" 64 | for i in range(len(phrase) // 3): 65 | word1, word2, word3 = phrase[3 * i : 3 * i + 3] 66 | w1 = cls.word_list.index(word1) 67 | w2 = cls.word_list.index(word2) % cls.n 68 | w3 = cls.word_list.index(word3) % cls.n 69 | x = w1 + cls.n * ((w2 - w1) % cls.n) + cls.n * cls.n * ((w3 - w2) % cls.n) 70 | out += endian_swap("%08x" % x) 71 | return out 72 | 73 | @classmethod 74 | def get_checksum(cls, phrase): 75 | """Given a mnemonic word string, return a string of the computed checksum. 76 | 77 | :rtype: str 78 | """ 79 | phrase_split = phrase.split(" ") 80 | if len(phrase_split) < 12: 81 | raise ValueError("Invalid mnemonic phrase") 82 | if len(phrase_split) > 13: 83 | # Standard format 84 | phrase = phrase_split[:24] 85 | else: 86 | # MyMonero format 87 | phrase = phrase_split[:12] 88 | wstr = "".join(word[: cls.unique_prefix_length] for word in phrase) 89 | wstr = bytearray(wstr.encode("utf-8")) 90 | z = ((crc32(wstr) & 0xFFFFFFFF) ^ 0xFFFFFFFF) >> 0 91 | z2 = ((z ^ 0xFFFFFFFF) >> 0) % len(phrase) 92 | return phrase_split[z2] 93 | 94 | 95 | def get_wordlist(name): 96 | try: 97 | return WORDLISTS[name] 98 | except KeyError: 99 | raise ValueError("No such word list") 100 | 101 | 102 | def list_wordlists(): 103 | return WORDLISTS.keys() 104 | 105 | 106 | def endian_swap(word): 107 | """Given any string, swap bits and return the result. 108 | 109 | :rtype: str 110 | """ 111 | return "".join([word[i : i + 2] for i in [6, 4, 2, 0]]) 112 | -------------------------------------------------------------------------------- /tests/test_offline.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from monero.backends.offline import OfflineWallet, WalletIsOffline 4 | from monero.wallet import Wallet 5 | from .base import JSONTestCase 6 | 7 | 8 | class OfflineTest(unittest.TestCase): 9 | addr = "47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef" 10 | svk = "6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901" 11 | 12 | def setUp(self): 13 | self.wallet = Wallet(OfflineWallet(self.addr, view_key=self.svk)) 14 | 15 | def test_offline_exception(self): 16 | self.assertRaises(WalletIsOffline, self.wallet.address_balance) 17 | self.assertRaises(WalletIsOffline, self.wallet.balance) 18 | self.assertRaises(WalletIsOffline, self.wallet.balances) 19 | self.assertRaises(WalletIsOffline, self.wallet.export_key_images) 20 | self.assertRaises(WalletIsOffline, self.wallet.export_outputs) 21 | self.assertRaises(WalletIsOffline, self.wallet.height) 22 | self.assertRaises(WalletIsOffline, self.wallet.import_key_images, "") 23 | self.assertRaises(WalletIsOffline, self.wallet.import_outputs, "") 24 | self.assertRaises(WalletIsOffline, self.wallet.incoming) 25 | self.assertRaises(WalletIsOffline, self.wallet.new_account) 26 | self.assertRaises(WalletIsOffline, self.wallet.new_address) 27 | self.assertRaises(WalletIsOffline, self.wallet.outgoing) 28 | self.assertRaises(WalletIsOffline, self.wallet.sweep_all, "") 29 | self.assertRaises( 30 | WalletIsOffline, self.wallet.transfer, self.wallet.get_address(1, 0), 1 31 | ) 32 | self.assertRaises( 33 | WalletIsOffline, 34 | self.wallet.transfer_multiple, 35 | [(self.wallet.get_address(1, 0), 1), (self.wallet.get_address(1, 1), 2)], 36 | ) 37 | 38 | 39 | class SubaddrTest(object): 40 | data_subdir = "test_offline" 41 | 42 | def setUp(self): 43 | self.wallet = Wallet( 44 | OfflineWallet(self.addr, view_key=self.svk, spend_key=self.ssk) 45 | ) 46 | 47 | def test_keys(self): 48 | self.assertEqual(self.wallet.spend_key(), self.ssk) 49 | self.assertEqual(self.wallet.view_key(), self.svk) 50 | self.assertEqual(25, len(self.wallet.seed().phrase.split(" "))) 51 | 52 | def test_subaddresses(self): 53 | major = 0 54 | for acc in self._read("{}-subaddrs.json".format(self.net)): 55 | minor = 0 56 | for subaddr in acc: 57 | self.assertEqual( 58 | self.wallet.get_address(major, minor), 59 | subaddr, 60 | msg="major={}, minor={}".format(major, minor), 61 | ) 62 | minor += 1 63 | major += 1 64 | 65 | 66 | class AddressTestCase(SubaddrTest, JSONTestCase): 67 | addr = "47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef" 68 | ssk = "e0fe01d5794e240a26609250c0d7e01673219eececa3f499d5cfa20a75739b0a" 69 | svk = "6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901" 70 | net = "mainnet" 71 | 72 | def test_subaddress_out_of_range(self): 73 | self.assertRaises(ValueError, self.wallet.get_address, 0, -1) 74 | self.assertRaises(ValueError, self.wallet.get_address, -1, 0) 75 | self.assertRaises(ValueError, self.wallet.get_address, 1, 2**32) 76 | self.assertRaises(ValueError, self.wallet.get_address, 2**32, 1) 77 | 78 | 79 | class TestnetAddressTestCase(SubaddrTest, JSONTestCase): 80 | addr = "9wuKTHsxGiwEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N" 81 | ssk = "4f5b7af2c1942067ba33d34318b9735cb46ab5d50b75294844c82a9dd872c201" 82 | svk = "60cf228f2bf7f6a70643afe9468fde254145dbd3aab4072ede14bf8bae914103" 83 | net = "testnet" 84 | 85 | 86 | class StagenetAddressTestCase(SubaddrTest, JSONTestCase): 87 | addr = "52jzuBBUMty3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6" 88 | ssk = "a8733c61797115db4ec8a5ce39fb811f81dd4ec163b880526683e059c7e62503" 89 | svk = "fd5c0d25f8f994268079a4f7844274dc870a7c2b88fbfc24ba318375e1d9430f" 90 | net = "stagenet" 91 | -------------------------------------------------------------------------------- /docs/source/seed.rst: -------------------------------------------------------------------------------- 1 | Mnemonic seeds 2 | ============== 3 | 4 | You can utilize the ``Seed`` class in order to generate or supply a 25 word mnemonic seed. From this mnemonic seed you can derive public and private spend keys, public and private view keys, and public wallet address. Read more about mnemonic seeds `here`_. 5 | 6 | The class also reads 12 or 13 word seeds, also known as *MyMonero style*. 7 | 8 | .. _here: https://getmonero.org/resources/moneropedia/mnemonicseed.html 9 | 10 | .. warning:: This class deals with highly sensitive strings in both inputs and outputs. 11 | The mnemonic seed and it's hexadecimal representation are essentially full 12 | access keys to your Monero funds and should be handled with the utmost care. 13 | 14 | 15 | Generating a new seed 16 | ----------------------- 17 | 18 | By default, constructing the ``Seed`` class without any parameters will generate a new 25 word mnemonic seed from a 32 byte hexadecimal string using ``os.urandom(32)``. Class construction sets the attributes ``phrase`` and ``hex`` - the 25 word mnemonic seed and it's hexadecimal representation. 19 | 20 | 21 | .. code-block:: python 22 | 23 | In [1]: from monero.seed import Seed 24 | 25 | In [2]: s = Seed() 26 | 27 | In [3]: s.phrase 28 | Out [3]: 'fewest lipstick auburn cocoa macro circle hurried impel macro hatchet jeopardy swung aloof spiders gags jaws abducts buying alpine athlete junk patio academy loudly academy' 29 | 30 | In [4]: s.hex 31 | Out [4]: u'73192a945d7400a3a76a941be451a9623f37dd834006d02140a6a762b9142d80' 32 | 33 | 34 | Supplying your own seed 35 | ------------------------ 36 | 37 | If you have an existing mnemonic word or hexadecimal seed that you would like to derive keys for, simply pass the seed as a string to the ``Seed`` class. Class construction will automatically detect the seed type and encode or decode to set both ``phrase`` and ``hex`` attributes. 38 | 39 | .. code-block:: python 40 | 41 | In [1]: from monero.seed import Seed 42 | 43 | In [2]: s = Seed("73192a945d7400a3a76a941be451a9623f37dd834006d02140a6a762b9142d80") 44 | 45 | In [3]: s.phrase 46 | Out [3]: 'fewest lipstick auburn cocoa macro circle hurried impel macro hatchet jeopardy swung aloof spiders gags jaws abducts buying alpine athlete junk patio academy loudly academy' 47 | 48 | In [4]: s.hex 49 | Out [4]: u'73192a945d7400a3a76a941be451a9623f37dd834006d02140a6a762b9142d80' 50 | 51 | 52 | In [5]: p = Seed("fewest lipstick auburn cocoa macro circle hurried impel macro hatchet jeopardy swung aloof spiders gags jaws abducts buying alpine athlete junk patio academy loudly academy") 53 | 54 | In [6]: p.phrase 55 | Out [6]: 'fewest lipstick auburn cocoa macro circle hurried impel macro hatchet jeopardy swung aloof spiders gags jaws abducts buying alpine athlete junk patio academy loudly academy' 56 | 57 | In [7]: p.hex 58 | Out [7]: u'73192a945d7400a3a76a941be451a9623f37dd834006d02140a6a762b9142d80' 59 | 60 | 61 | Deriving account keys 62 | ---------------------- 63 | 64 | Once the ``Seed`` class is constructed, you can derive `all of the keys`_ associated with the account. 65 | 66 | .. _all of the keys: https://getmonero.org/resources/moneropedia/account.html 67 | 68 | .. code-block:: python 69 | 70 | In [1]: from monero.seed import Seed 71 | 72 | In [2]: s = Seed("fewest lipstick auburn cocoa macro circle hurried impel macro hatchet jeopardy swung aloof spiders gags jaws abducts buying alpine athlete junk patio academy loudly academy") 73 | 74 | In [3]: s.secret_spend_key() 75 | Out [3]: '0b7a7bac8a5b6de2f483d703ef82b1bb3e37dd834006d02140a6a762b9142d00' 76 | 77 | In [4]: s.secret_view_key() 78 | Out [4]: '75ec665f4912cec813ff7f20bc75b1f375ee2f8d4bb7631ae8d1af302732a609' 79 | 80 | In [5]: s.public_spend_key() 81 | Out [5]: 'd5db200426637399f0076090dea01394afc2b157f94d287516911dbbcf8b2275' 82 | 83 | In [6]: s.public_view_key() 84 | Out [6]: 'cd235f236224b8a5f1e12568927e01a2879bfd49cec2517b0717adb97fe8ae39' 85 | 86 | In [7]: s.public_address() 87 | Out [7]: '49j9ikUyGfkSkPV8TY66p2RsSs6xL7NR5LauJTt7y6LZLhpakUnjcddUksdDgccVPEUBk2obnM1YUMaXKsGsCnow7WYjktm' 88 | 89 | 90 | 91 | 92 | API reference 93 | ------------- 94 | 95 | .. automodule:: monero.seed 96 | :members: 97 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_transactions_single_pruned.json: -------------------------------------------------------------------------------- 1 | { 2 | "credits": 0, 3 | "status": "OK", 4 | "top_hash": "", 5 | "txs": [ 6 | { 7 | "as_hex": "", 8 | "as_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 21232548, 2296164, 2442168, 193665, 6538, 3230, 191, 12317, 647, 1020, 274\n ], \n \"k_image\": \"f1c7bd0270a937f67b9b06bdf6707d13c75c0a2563e67c48eb804adc04b8d660\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 13137123, 11881714, 643349, 421652, 7427, 63245, 13648, 15277, 3469, 433, 1602\n ], \n \"k_image\": \"d6cd646f7dcb6dcdb7f4828227e7598fb106750c01e4c778a458b3e18ac632db\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"c4a1a768bbc3434bece66a3ec7d258fe21dea9a3b5ecd22efae77d1bc9d7d1fd\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"96ca9e5b118639704881f99fa7a69e30e5c46b457386ae1db0ac1ea0872fa393\"\n }\n }\n ], \n \"extra\": [ 1, 251, 255, 38, 23, 61, 92, 77, 155, 139, 43, 198, 150, 232, 179, 116, 24, 65, 55, 74, 97, 144, 85, 197, 69, 59, 12, 71, 138, 232, 120, 230, 116, 2, 9, 1, 136, 122, 195, 120, 69, 251, 156, 30\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 78370000, \n \"ecdhInfo\": [ {\n \"amount\": \"15279cd867dcd9ac\"\n }, {\n \"amount\": \"9ea455993afc7ed7\"\n }], \n \"outPk\": [ \"30e2fd204997ef76294bca451f8f061cc3cbf0432aca6b9e3e71c567feb4a698\", \"79af72a1d671bfb5d343f7131f1c5796746441da04c9aab2fb1716fb2bc640bc\"]\n }\n}", 9 | "block_height": 2279770, 10 | "block_timestamp": 1611281602, 11 | "double_spend_seen": false, 12 | "in_pool": false, 13 | "output_indices": [ 14 | 26189357, 15 | 26189358 16 | ], 17 | "prunable_as_hex": "", 18 | "prunable_hash": "e4871fbdf5f5eb65095f8c41ed8573007fa81b8291841561a031c9beaea68f00", 19 | "pruned_as_hex": "02000202000ba4f78f0ae4928c01b887950181e90b8a339e19bf019d608705fc079202f1c7bd0270a937f67b9b06bdf6707d13c75c0a2563e67c48eb804adc04b8d66002000be3e9a106f299d50595a22794de19833a8dee03d06aad778d1bb103c20cd6cd646f7dcb6dcdb7f4828227e7598fb106750c01e4c778a458b3e18ac632db020002c4a1a768bbc3434bece66a3ec7d258fe21dea9a3b5ecd22efae77d1bc9d7d1fd000296ca9e5b118639704881f99fa7a69e30e5c46b457386ae1db0ac1ea0872fa3932c01fbff26173d5c4d9b8b2bc696e8b3741841374a619055c5453b0c478ae878e674020901887ac37845fb9c1e05d0a9af2515279cd867dcd9ac9ea455993afc7ed730e2fd204997ef76294bca451f8f061cc3cbf0432aca6b9e3e71c567feb4a69879af72a1d671bfb5d343f7131f1c5796746441da04c9aab2fb1716fb2bc640bc", 20 | "tx_hash": "bbc10f5944cc3e88be576d2ab9f4f5ab5a2b46d95a7cab1027bc15c17393102c" 21 | } 22 | ], 23 | "txs_as_hex": [ 24 | "" 25 | ], 26 | "txs_as_json": [ 27 | "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 21232548, 2296164, 2442168, 193665, 6538, 3230, 191, 12317, 647, 1020, 274\n ], \n \"k_image\": \"f1c7bd0270a937f67b9b06bdf6707d13c75c0a2563e67c48eb804adc04b8d660\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 13137123, 11881714, 643349, 421652, 7427, 63245, 13648, 15277, 3469, 433, 1602\n ], \n \"k_image\": \"d6cd646f7dcb6dcdb7f4828227e7598fb106750c01e4c778a458b3e18ac632db\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"key\": \"c4a1a768bbc3434bece66a3ec7d258fe21dea9a3b5ecd22efae77d1bc9d7d1fd\"\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"key\": \"96ca9e5b118639704881f99fa7a69e30e5c46b457386ae1db0ac1ea0872fa393\"\n }\n }\n ], \n \"extra\": [ 1, 251, 255, 38, 23, 61, 92, 77, 155, 139, 43, 198, 150, 232, 179, 116, 24, 65, 55, 74, 97, 144, 85, 197, 69, 59, 12, 71, 138, 232, 120, 230, 116, 2, 9, 1, 136, 122, 195, 120, 69, 251, 156, 30\n ], \n \"rct_signatures\": {\n \"type\": 5, \n \"txnFee\": 78370000, \n \"ecdhInfo\": [ {\n \"amount\": \"15279cd867dcd9ac\"\n }, {\n \"amount\": \"9ea455993afc7ed7\"\n }], \n \"outPk\": [ \"30e2fd204997ef76294bca451f8f061cc3cbf0432aca6b9e3e71c567feb4a698\", \"79af72a1d671bfb5d343f7131f1c5796746441da04c9aab2fb1716fb2bc640bc\"]\n }\n}" 28 | ], 29 | "untrusted": false 30 | } 31 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcwallet/test_sweep_all-00-get_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "subaddress_accounts": [ 6 | { 7 | "account_index": 0, 8 | "balance": 111141601989972, 9 | "base_address": "56cXYWG13YKaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkeUVogGN3", 10 | "label": "Primary account", 11 | "tag": "", 12 | "unlocked_balance": 111141601989972 13 | }, 14 | { 15 | "account_index": 1, 16 | "balance": 1000000000000, 17 | "base_address": "79kTZg96pMf2Dt9rLEWnLzTUB8XC1wMhxaJyxa79hJu6bK9CfFnfbSL1GJNZbqhv9xPqJhRj2Yfb7QUWa2zeEw56H4KiUfN", 18 | "label": "Untitled account", 19 | "tag": "", 20 | "unlocked_balance": 1000000000000 21 | }, 22 | { 23 | "account_index": 2, 24 | "balance": 0, 25 | "base_address": "73WHDaR3q9D6x8mpUrP8Ux5t2LhF5Mcq9URJ3nRjhkUfAwkLxLFVvkebG8baqxbsrMjHv549rPYsKc6nZGZo5wad1pGRr2J", 26 | "label": "", 27 | "tag": "", 28 | "unlocked_balance": 0 29 | }, 30 | { 31 | "account_index": 3, 32 | "balance": 0, 33 | "base_address": "73BEwtAeC3pCJwK5cmJLAy4vwCkLLKmfgXW2XsCC7dsZPFwMtA7Sr7ZQWFTAYDFzQFXzcVNTm5jXGb4GqTzJGB7AE7DfwJn", 34 | "label": "test", 35 | "tag": "", 36 | "unlocked_balance": 0 37 | }, 38 | { 39 | "account_index": 4, 40 | "balance": 0, 41 | "base_address": "75WFcdiyf3z9hvMDu1RDmMFVgRaes41p7GftarFtPet5A9srdWuJpoJCiJyNMAq2oMfyUHLhCDmw4g6Typks3rVw4ipBiK1", 42 | "label": "", 43 | "tag": "", 44 | "unlocked_balance": 0 45 | }, 46 | { 47 | "account_index": 5, 48 | "balance": 0, 49 | "base_address": "79YfcwoWUBebbnR31sD4Q1FryHUrxExNqjAKukfAnvisLKtyZTVFqwtNcb33hsfrUF58esU7isA831SvWfx6PpDX2mpMPmR", 50 | "label": "test", 51 | "tag": "", 52 | "unlocked_balance": 0 53 | }, 54 | { 55 | "account_index": 6, 56 | "balance": 0, 57 | "base_address": "79khAXM9X3g6wWv7PBJVsUWqA8kmgGwXiicTsN1856uo17V4ijXTGVfGA3stnB5ur6a3M5rm1Nvdjacj5wM4NzHA6WGyLCT", 58 | "label": "", 59 | "tag": "", 60 | "unlocked_balance": 0 61 | }, 62 | { 63 | "account_index": 7, 64 | "balance": 0, 65 | "base_address": "78ZoiX5PoxHNLosKUbu8tv3nWSEhT6g1iam5weemsWvPCRPKGSpFJYRirbBz3hJs49R5QM7CpHSnwCwCPX9Qf4PGSachsDh", 66 | "label": "test", 67 | "tag": "", 68 | "unlocked_balance": 0 69 | }, 70 | { 71 | "account_index": 8, 72 | "balance": 0, 73 | "base_address": "72RBtJcgdtcXWHoWUijK6qfBCRRw2LSJGAeCgfe1eAFZ1Bjtrwwu1GZTQSV3muhhtMjXsAnSYT8mg8CGSCM9B5cT9CeCP2t", 74 | "label": "index 8", 75 | "tag": "", 76 | "unlocked_balance": 0 77 | }, 78 | { 79 | "account_index": 9, 80 | "balance": 0, 81 | "base_address": "77zDStiZ6XRGGMQ2nKhZi6ewQpaaJhWviQoz9pMrmRvd4yPRJhDurmB74B74pj2oLjcmpta56JFRaAYYBC2hzF8yCpQ7tXA", 82 | "label": "index 9", 83 | "tag": "", 84 | "unlocked_balance": 0 85 | }, 86 | { 87 | "account_index": 10, 88 | "balance": 0, 89 | "base_address": "7B35hT7MnqUP1uQJaUEZSVWmkcZAUVxXzX3JqKDhhjGv4AVSVWw6AiTJ39a1AjtdWPgGCp1JkgPiDMKM9ktgU7Dx5pGfbdY", 90 | "label": "index 10", 91 | "tag": "", 92 | "unlocked_balance": 0 93 | }, 94 | { 95 | "account_index": 11, 96 | "balance": 0, 97 | "base_address": "7BraWKhjmaW8ckBYVVmiVbQ3J3cFeKs5JBPTkGcECrHnAq8TwPDRoQe3iv4UDC3Woc7NvVTKZFVNeAQjJws9JEQeVHyoApz", 98 | "label": "index 11", 99 | "tag": "", 100 | "unlocked_balance": 0 101 | }, 102 | { 103 | "account_index": 12, 104 | "balance": 0, 105 | "base_address": "7Bwqgh7k9w1Xujaxg6v2j4bc73UQKxPq6UiZnh5B7pir5zDws2RunYTN7p8mhbLX6s3M7umCmYvMKSGmfaL5ecMsMBtYFVW", 106 | "label": "index 12", 107 | "tag": "", 108 | "unlocked_balance": 0 109 | }, 110 | { 111 | "account_index": 13, 112 | "balance": 0, 113 | "base_address": "73dhJABxD5sgcuAW3yiQbGLYQWx8SHxK4EmzmuCanRDBJpBR9Ste3BiZU2ZpPyqWbYer1a63N5NFdVWz7LxWxLoJFD9ozp1", 114 | "label": "index 13", 115 | "tag": "", 116 | "unlocked_balance": 0 117 | } 118 | ], 119 | "total_balance": 112141601989972, 120 | "total_unlocked_balance": 112141601989972 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/data/test_offline/mainnet-subaddrs.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef", 4 | "84LooD7i35SFppgf4tQ453Vi3q5WexSUXaVgut69ro8MFnmHwuezAArEZTZyLr9fS6QotjqkSAxSF6d1aDgsPoX849izJ7m", 5 | "89U1fxtSjwuF1UQmC1bYspT8dMgQLc9433VUrjcaLQ9w4dK6Jb1Wc3YdwejuCmdY451Z3eRiXg3zcSE2aQLGhsYECo2bsiQ", 6 | "88pqQ8Vwjwpiw3HA8Xvr575gakn4KH3MvCmt2HgAn2ywc8YxhpKFjbJQsX7eR7hMBSUKRJ6VGzZBsQMoHKcVf6fDVSvKb5y", 7 | "89h4ii3UQoKEF2ytqEaG1cSPinEiwc5iRiRL6JomXGijiag4bo93jjuFmyfBj9kiE1ZU2dE8Yb5SLaRyCMc2mBTHJHduV9N", 8 | "88LWznBFZh5XGNpqZXx1GJZ9hYgd8N8qvUdEgiy1fXfEYuTyenhdUe24itzekyydSxKY65GaT8taWCEzHK87Ky6ELxZPzKU", 9 | "83b4QihSFfDbs65VBh36VWFJHPLrmwdN6UfX4wHX8UC2NX2wBkoCX7MVGNbUgmWVUq9zqj8PM2SQmRoSsFRHFy3K9f8J1NU", 10 | "86ycKZVPr7EdncyoeM2SzUZCQ24Nzz8WW4WapfYxWomgDJj4wUQ6i581m6naAEW8gvRVrHucze9FvEVBGVcnYYzo6TupW6S", 11 | "8AuUoksJzoF19LJvsmX8YR6vBJWYkzteMcJGRy76iwjWZcSg41uyfzC9rhWfJeT9NNWjpUmhDHKw9aoDBgLjnSe1PQwjvWZ", 12 | "8AXCb69SM49PNQcQSeVB6JEc3qR7cXExThCw8ox4Mpo3TX5FvD8WRGNM2BgpApPybRG1cdvNqmswN2rGx46TBm7b4staEEV" 13 | ], 14 | [ 15 | "83MdKpu2ANWNrbT8Heg7UPbJ4sUM3TEUX2oTRFrgFrC1Pdq4GLhf6FG8p2o2E2zguw5EkQHApqGyCifncLMyi2Jr3UUiGyE", 16 | "84kuGxcFKzdRoN5X6HmWbX6gwHXoDKfqBJoUSCu9ifejLYeEQSzu8ViLt4SJBss1ijT9Duk8Awn5tKFy7sQxWvLcDAR2bNz", 17 | "8B1ktYJrKtBJzE5dds8Fg3EjMPo9dNKtJNePgLFDqqR6EjCZnhjZCWF88wVwcHdLJK4BrzYWV81PZHUztnsh9zisAsTSppv", 18 | "83VX1Q16Zbv1ewS3sxbSt9Cjm4Z38m4sYNh7SVPVVtak1j4WzDqq5XzNa1NuEeWTBZjJRYwmjkHorHHACY3BBrTr6Ftbr8h", 19 | "82Y7EaHa3Td77uCMFtL5MhWS6svQPE1Gvh89dnRLXSjD4tTPbaSsXk9gqGYEkrwXsqH7zXpoHZR8MZVDC8isM3L3JWtPkFM", 20 | "85oDKs2QpUWHZ99hR4rFThSBujnMDAREvEUb2ooLgZGd6sY1vkMUGETSK7gYWBmwWEM61XxpXuj8cSnh4dCsGVVTB4LUY4q", 21 | "86q2xdB6MUP8YeFPuS4aqVNkKMxwmKczrSN2mg3FN7JNVbMYjcw3FyNSbL5GCqrBdPHWpk9efKyw9LkNuXfRm3rY915j85s", 22 | "83jHFZ9RBJk33dMinJ7HJFCbVF49v5cdZev873iQvABTSme6pEBF3AR2MmrkjcUuv6NZi1UV79UYf1QFhy21iF9EUua9nVW", 23 | "8BWsU8ezPMf6adzVvxFurLekoARoFJLieAm6aycTwuAo6AGu1sMfdtW1diHhYgogq9Soon7YwAnPT2pk9cEsZgq19qmHS8M", 24 | "87EVWV1MCF3C3j9tYBP7YTcU8ML8nXHSWXkDWBuXPXjkVXpGMEUqdYfGshSdHUUgYca2upy6zTo6v9oX8Qt4Kje5CpACB65" 25 | ], 26 | [ 27 | "82vEjyhAtn58qSkoMKcdG4XFMxHbRwatbBvLcHEL774f2RoDR9yduUhPCPR864RGrgXM2awr6SjeJChCW3jv3BtEMrQpkbp", 28 | "85mB3RjnZEoUC1mi4TxvRi8HEb3FqJf974sEzmsGPrQVMF8GDiEqY3PMgQuqjZsDY9MzCpQPQT5SmGkWYyWpzrRG9yP57ip", 29 | "865XMywqZgufky63ZWPU5DALsUd33nsn2NhsznUoBsNDUgxz9upvUQsUkwmJ2RZVZef31VdV82qKRe7BkfNsmWvRVogrAN9", 30 | "87tJKsNVuACb6tbz2mz6VdPBAKZzCDrrCC2ywdqh1FCRCHpjMNRAP4c2uLWX6nboyjVP6sTU1YR5UYCBkqLgpVWQSBeumvd", 31 | "8Br4ZBkj6AVEWnvyqMuoE5AJMYtNncqy5PcKew88mTGPJSCVZyoHvSvDK9cTSQ5cZ1Un9tjScY718bHqEUauPhQC7fA4Abv", 32 | "8677kud5PZ3fTMwQryC1Tw7HMLKLYpPkQE9zLbyuZTGB3vumdfD6Uh72uqjn7MopfTAB73vbMS8YdhrRRC5FJYb9Jy4N789", 33 | "88BAAY8e45H6mAiUk3TafLL3HXCu9FdNQXEe59MY7ogFNYJAgF2NzPVTqT8ppnCD9h4ks3QRx3aMeMHFPEZb9VXiUx7aSzf", 34 | "859jYzfKGCAfjaFzR3xAeSWtxvJA6PRppjVYGFMXThoN5jym82j6zVjRbJZ7EDcRcfg6dJwAwLeefFEuXnB8PeHHGt325FM", 35 | "8AT57c4dPboJU85RBLeS3mAQAuHxUqZvKBqsi7pEFA8X4jhxqESKLt9cfd5477iqcZNgCgCGfc6RBAjicfAiqQ8QQLVjECj", 36 | "87m4XYZn3x3Hf3vHsNyt9R9CFR8QrAVCZKeYPmEtftwgLU1ggQ3h17g8yUmSxKdgLGKtyEwpoMkFe2HQ1gSA2vd29ZpAJgd" 37 | ], 38 | [ 39 | "85pksp3TvcreViEHc7KkB9UEQDie7Zduc6abeKxMsY6uUE1WtjwCkMdGLY5pWmifqCEh7Me8Xa59tCjmhR4oDtPCQ4nwkwj", 40 | "88rzK1dpNHY8EMYgwydsRiHNr3UfMKsehYKCG1B3dV2rNmn7fPykXq5MoixjCSU1qmDtcLghuvg9EXrYHA4Ej9wk9dVJe59", 41 | "87kqU8apGto6rZ5ZwuuW1pLDmnircUB27XYSGFQF4D8vcZr87cM9MkNJJGShREYSLR2MiK9WXJAvGWBDT47C93Td2rCZfCu", 42 | "8BcNaua6uG6RSUs4vhJ4uUC5fG3yaiSp2SYP4DdEaDTkdtJgXrTH3ZcYdgCPtUYELc76znQADhDhaNmi9w37Hc2e6tLd13k", 43 | "8AUZCjSAwp31YKmsgd4MZEWKTcDVFL4Xg5FFvVoDV2oWBdLgeozA8JLNT2GQTLZKrzcBPNfPFadDBFjwnjR5TPTZ6UhLJGk", 44 | "8ALkYCkxwnE6ZnbJyEqnmXTWFh7owB4MWjZsZbkjbMtodBf9E8etmobfTn2dUYd6vr98SgZAnSudZXnX3GubKzU5F8X7bgU", 45 | "83HnemUBptw1MAzUpCuU1PaMDzpj17K4sFdcCPDMmvXAKcC4BdHwHib3xmARdCeMr1ZVVZUgWTzusjBsKoEoSKnoFrrfEgf", 46 | "85xU6QncBsjiBeiUVs7df5dRBH6dLwN7m1oLC1ZnQAuQ2vKbTmczL4wegnaFZLKtWhCtDbWQYgStwj2raHUcfDtaDZEfKx9", 47 | "87DRqkCNPW11XWeyozsToXae5nSKxPDEY3my3gFQ1Yb7RXqsEWZ5BbWaDgcfMCnjd5RQUDoDqe8MkMeHNvsCMGdtKyLsfJL", 48 | "8BBboWcfWXNh2fNTNiMUZeaYABYQe9DNm9MPHKywogFnHk3VxTk2yNAHYisjeZ5LoFZerCpGwuburgknDjUXa2P13DRaegi" 49 | ], 50 | [ 51 | "87Schz4up6jEpfbHjia2q7VzruvGdhLnJPkyLDrWJnuzBg7KuPPBQk32utS6xA8crgW8PPVqoLT3tLyRPfemT4j4GosT7w2", 52 | "8BWmh7TkZeNewjSHauaWLFYyNuaSzFTNRNrcwUoFixMB2j1B8MdtcnpRf5sRtv6AgXhrkJvW75TdA1gHTC1iZq9vLXirMNS", 53 | "89mcRTWF1XCeu7dWaQr5C1S3fZ6pyWki2jRVhaybgwf34d8rgkUsaDvj6DwLeziLar787tsMBxfQgUFfhsSLek1PNCkE8JA", 54 | "89bfr4NXY4D8jczTGpVxn5YhUbDohyJ6E3RQ3vzie4NMAwdU7EmCj9n82bn7k8z7krZsih2yhtUHhiwRBm9QofSU166F453", 55 | "87bGcZzkTwLUJRTjuG7yhdUe195Er5aqY3yaUWZwrgRMUn6MTDG3euP9TwuM1TqqRMPAquhF7abayidBrr64mTJj5LELx9T", 56 | "84kGzr5mv65UrNzqYnwjkxiiGEciCYeiE3fMZMfRLZt1LN9hc5idZT3cXyF6VKhcseYND4hA7Db3mabxvoUTPGHh4FGDUX7", 57 | "89XMhHqZDsCBES3uSznQT1gbqi6GnfFLfAaeftGoqtDd5ei6ovBQk128ic1NWFkXGLCPK6BHRxBJ64tpa8uasR1h2XX1Viw", 58 | "85ENJqzHuycgLL64xqxvDhbRhekk9pXCt86AEVBqUFo4Rqxfe7ffyHYZrhryXpgZtV7ceFDY8N53bh7beRecb2NxKvtM7CP", 59 | "84qSwP2XSTK7ijaDwtFUd6eF3NqQfW8ET31oL13GGv4XRpA35aFqYUUcXspsSJYeNXQxGJGCzEcLsCRoYM9ok2ta9MBhgUv", 60 | "84nTtTGVwaCcCVe7uUSkshC9Mbbt4EEXkYnJ1uQJrH4xE7YHLAeyJUTiFRJ19dAQwU8qehtrUEWc5AkcTAbqUuowMEhBLmP" 61 | ] 62 | ] 63 | -------------------------------------------------------------------------------- /tests/data/test_offline/stagenet-subaddrs.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "52jzuBBUMty3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6", 4 | "7AeQwvrLtPeYoXVPRkEu8oEL7N9wnqHjYKwSvTf6YKbHgYmw6AJMsjggzVLo21egMK9qcoV1mxCTfP4FbaGb7JEMDfpLetk", 5 | "73YMtNiQmazP56aYSAR2SjT6uCfCNEBFnfYc4PHUUJaqC8urLbipC1Z3NUUqoraQV4PJmLRHV81cnadnoUAcLiqD8jQB4mL", 6 | "7Ab6cPPwU97LD8HwX1X8QSJGLtkLq4vYHG5RFYiLXAvvX74yHoMxPGYXPchEmeQaVGjT72mXBYouNA8HLSvnxdP9LD931f3", 7 | "73g1pYwHUuPYLW7AbvpGAJGhyJBUEZyiGhMbh3WPMHp8gLAhk8vN4Z7dKEorEiredWZSfC6zbKy8qMSzA51pHL6M8TT2ECH", 8 | "77P8NdNM9gZVYEy8ieogWY8oDr5tpkBb3AvHSy4pPPktJMGjBeQsKzjb4K2k4g8hkM6vZrgSuH6UC1Diq27upfCSJBUkKkd", 9 | "782sDPUzzbgVbc5iDR7hziFVXTpCdwL4SDzn62MjVDZaHPhp2QDjmcwjb4N5nEapVKHzXkd5PkuB42iKRjkF6HEf5xcfeQL", 10 | "7BAZYSZKLjii4MhRivcNqSV2TLsKdhtr2TSBVUdgjnkDSctE75vBsdoGuJ1QL7NV3y7wsrBq8nTcCZdRRfHbmuVxE3iAd3x", 11 | "7AZJHyGefLwcYVnyaeGaNgPRLVP6oDaNRRmknyLckVCANoZp6MmsB4YX8BZcg3AjyXAM3JwM1SYRvhwH5stCVL2cA23JPzx", 12 | "73KFRi9wGafL6osnpjAn4UiyU2WEDVgfKSfSnou8oV4jdEg2CQHdb99YDWS3nLN7DwQaERM7pZaq1AHVz42fJ2dm3eamP1s" 13 | ], 14 | [ 15 | "73WqhTXVZM6Rq9TLi2EQHGK5jwopSRNHheT6JXCfmg9DVPps4um6En5Yn1RMcKezSM3iyUZYDEnEdamWPABSpRS1Eq7gnsr", 16 | "7BeLmCeBQ1LHXYxUyApGJf3nj6TjGDTEX3NtbdFfNg2SKewZFcDFZnFUgJy5kFNAig2CEXAo3wwGodDvknhZ3R6VE1w1i5h", 17 | "77ebau9MYFkN8YNNhkC9msLEKJh42XdrP21ScYtayPRkEkNfwdMqr3eNV8UEkxrcVtM7psu1XzUYWZB7HPApuVyw91dXjcp", 18 | "733Yfh5QvoFiHwE6unFnHD6ZZcegJQPdGZL5sxtBYVRv6UmCnbb8uTGBZeeW6Ua3sCVaz8TUtCux1UXLEbaLPSQVNLRgwwV", 19 | "75VewnRuMctjjJNs6kHPRcAwXYDC4xPq1QDGnWBKtDBRFDJ9EHRJ4uvbaYKBXXvsXR8Hja28LAmftQ1jhFq6GAXb749pftb", 20 | "75vUzzztkyRFvRmxU1Zc66dRt1SsgK5waAfFMHN4Y5r2bxUU4vLn8dPHMPbv1ZmcsANmd2p9ntkAzTxhXmsZEakb9ZqJiGP", 21 | "7AW25a5qHwDfJipEtw3C2j5mJYPxr1HiJYgb4gxeg4ac88hxPQtnzGqLnCtsPrpdn3PxvpNnBqHo2JR5DwJirgTvL6Qohrf", 22 | "77YaotboUyiYftkqN52mZbPV41eUbPvwj6EzKd4SUaBHSrYXVimYivzW9paJ1pz3zsMYkDPAMbSaUWo9ysHMdLo8EU15Lno", 23 | "73AExKobDPQTuYoAZ2CDBE71HnP33YQw3FDir36EV3JcTDgX4gVCCZqHZ17zMqX57uG3XpVXmBUkZGG7nJczfQaLHA2vznq", 24 | "73ttD7a5qDtTCeFx97BsiEHjEqZmrQ8ob5JrBGvPV8Gu3T349eaZ3ZnEvqUbTjV5qnNiCymZZTZwRcj1fTczkaBk7Dezqey" 25 | ], 26 | [ 27 | "78kWgiukLJxNFoydUFRFWJXspLE1SKMZN6A7NutrsQbXQZkCY9WwCnyRCgBCWG8nNYjAyLGDTe1gLdJgeGfaYomJGrPvMPT", 28 | "79TdTdLrhAVKDwbte5XHpd9Q1oJx7y5nUcDh5jYEHN8M7i9gHpLMDugYnesqKUZJ5BLuAkxnsSo56b5Y65jjhvnu3QrLMuf", 29 | "79aQMJ1zsCyHrHfZFv3KtCDRZM3RGuNF3BqJ79Q9BCzDGJ53bm2yej9AcqCj7TrqKHYjYvuLSxNV2h1b3zKDfRFJEB74bt3", 30 | "78Axv2Zd9HTaT8dxMcxF3U23nWGtsjUL2AUD4jA1u8c9AjhEs4qsBZCQDNavkNh6zy2nHw4md1GcwDXnJYEvNi8572uVn1e", 31 | "76oCbbYGsRwDw96uJCbkHPXigtXfk15azDmgqinBZ9pe8H7b9um4KYvPboMmhtPeE1Qt4eKnLmKqs13JhHBC5r6NAk6BxGP", 32 | "73gFMMdk59McfEhLhJEREZ5sLT2WtRrEGLvz4wVXkEe7infmwTUxQphFwXn4jscRvt4tVAnaxX7qoB253WVsV8hDRZ2uZoh", 33 | "78j5teCaMFh8U2SyyHPeoQ2ZuXzFYuwW3gTZmAa35deYbYKii1EfZrQHTgBbXztPrsXPVmP8tM6iQhkrVjCuT46BLk4x5d4", 34 | "78ACS1bi6mVUq8QfBY7hJwcL4LRgNBPPcR3v8h7hEhj81wUDLHHjPKjAWBhSJ97XRENP5CguNec528CLvfajfiCEJwUoTTC", 35 | "76CCd77MheahSt1jX6WfBN9xPvNrnX5MXUTzNepRa41cMfpHbccoFnN93h6vLjXTRY7sKp2ZFEqBBD15uqhcN8HXMusHKjn", 36 | "75RroztLkkeVGxzFt3wbZ2Dh77wmXaUiUMQSwMFfKKFPEqriHVzYEpkK9FMResvRmiTFyZMmCmfrgLqCcQ3DN7S2NTAaSwR" 37 | ], 38 | [ 39 | "79Tci63JsCjTVByYeACvo8fYVuV1eUAYvUiqUcq4ahMWbJ32KAKRt6u33YPiEmjBHSRLu3ycedGsPNS5ZPaz9iai7z3sRxk", 40 | "7B7JZefpcm24CmrrSJMNzkfJvfXMbzPXUeyQrSEJr4wW5kBWG3wqhSEgMfmFz4AuP8UZUB6m7reFnQRN6HkcEksPDaMNnJ4", 41 | "73wtz5eGxiV1pLGA7J8bSnbjDpGXZfEkQWugtEwP6bhuf7eW6g9UXgXKTsZqe78qDnNbzcdM3LVxg6Dh1nbXAReVTDsUVZy", 42 | "75fAk67wWr1GpTH11vvEVLSYw1MPxvTgPZSi2sSVZaA5CTxS2YmhW8k4rsgzf1kRj4AeAKrZxs2WBBxQdeS8kyJC7j8u199", 43 | "77NqmJoiak1VQZhep1bvce5i9HCriv9jAWmNPqGQ4Gcw3U1ycUo6eAj5f6CsmCRQmpNutG7yypidoSKmyb7FEaxXHPcGJ2D", 44 | "7BkT9XCvK8YXnTZp2jVQ4aPG5t8ibTP6rLPKgqncqmiDLfm2T4mnoLpPwBFh1C1Fn88L9cFzFzvSHdHacCZBwiv8D7dEYKP", 45 | "76ntFovqGQEVL4k1oKZMPNY3rWfvKaVhvbXg4KNQee7iZNZKZi5exF5bobqAJ4a3xmaicgBHmput4icW1q9FBJQJR66rALL", 46 | "738J577wiYTMQoSQhE5JaEey5dhDSXrH6aPLzwUAkxjR4DpxaMon6ED8Ynp9G2EFTTXRHDx7bhnFENpdAVBbufvi7o9k2rr", 47 | "75yKvJDzVKj7W3CdMVJRgpcGK8Rdesno2E1QJgitwb8NLRN9boGp8XeV5ZZtUbKgmm1PwHi1rtPR7NxCB5siS7eBCCmFJsm", 48 | "79uA9y4SiXBAJujB2MPzzTFPcZ2PfBdPFNzMERszuN1ZGtZzoTk9BB4MU1YErF9BGFQhqHUwbxyNYMvbkVtLuqiCNRYEiv3" 49 | ], 50 | [ 51 | "744ZzJeiqBMKtPdfLtTFR61H2mwZSRnZr8ovCNAFL2dF2vfyQ4AC2mifxDEm95HipZND6M5hhsiR66dTJuWAiQ7hJgHpZ5y", 52 | "752fmPXtgVcjV4nWBQFJWoZHB6vY3zfbgHgfLzLg8Zx87dJziiqSW7WcWzkpHqv4bCKfwaAjTpWKbjLDRboDYLwMPJVhQ2y", 53 | "73Jw9BDUxH9M75MamR9kskEKBDPfvjYMvTP1WK1o7KvJTEoWUMNewifEkS5SwjqKbLE7K9ELwac6PfVBP7pb4B7xD1nzpWS", 54 | "75aAVEyrKAhYo3SY3WDm15bXybQY3EP3D2SE4WfFU3doPEsatx8CSeV9Q3E76re9ZNKwSA9SAs9WFNrmAcpL79SnRnBiBVf", 55 | "763yuVT9DNYPK6b9agG15x6gVwRCPS5KjETpQ9snyJsPLKv1HoPgagMY8GauPTgfowfEiyKBudVWhCaKqfLgqnv2MUNuEUB", 56 | "75h6qSWVVthF5hNhE8wNDkjRSiw9R4wfX4sFxNgGw54K3QZQTQYs41HfbHJbx2G8gh5UPsXnJ7YkYgMou5eAH5fc1R2t99y", 57 | "75a5At6zLyW27m3yR2rjuNDLWzgMa3r9yE5yE49V2bDXUQxnwMggneff4tTofUM2JDZt7d1VQXMXm8zgJoGXgeMY5iTLT17", 58 | "79xa3UNp6dpAqyGpzZyiSFWocK4A3pDSSEEu8nLiDDzdXZfmR2MDUP5Wu544WjRS2nce2nrZhPZ3saEmHKtWXxxp9tfbPhJ", 59 | "759k7wLcsG8WGxc8QucsumgRTD8dApgUnh2WMyYYzg5UGGojtGUq4W6ZMZer7hn5vx6Nj8toj9eSUfJ8mXfWXYcSBr3szU1", 60 | "76gfDy2DkWZdkCQUbaxkqWDKvo1GE7AY6CLgv9D98ddkHiU2rszQWDi5jamMwhfPx4b16KSxogQJRZNeTaU2cR2i7PMKsiW" 61 | ] 62 | ] 63 | -------------------------------------------------------------------------------- /tests/data/test_offline/testnet-subaddrs.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "9wuKTHsxGiwEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N", 4 | "BaU3yLuDqdcETYzeF7vFSVEKNR4sSGxBV1Evrw5yNBf2VMiuAwfDmiF3RHqLHkaA5A6RGiNNRUqvtaqhMtdjA1SQ1tnQV8D", 5 | "BZSWhWTMA8TEJRZL4MopGLBrN2P9jAjnfTwbN2mRhFHmfUkZwpzJ4anKb1Wd2BjrUDSzzsbSt2bxAeXRvXvNsPT58TEDMh7", 6 | "BYe3G2jNpAdb2m5eD1y76Q7pd4WibxqvbfBhyo5sNgNUBPfac2Mu5M3FhdKmHVsMECQFR9bbWpf3TRXwEqeqK25JEnKskbc", 7 | "Bfs6mpyksF93RX1eDPhY3TWWKQKUvJUcygjhv8pVyNtifcTJMQw54gVGZYkk7P4nAyYeHb2Fjus7B1opGkeqdJ3t5SXkCAL", 8 | "Be1Vp5Y1TGs6eKF4UiqenUfQKkAMuytWzTd5oGQBVxKrRYJr99r1jZ6TeXgBXqpZ3AEuYDnLD639JYSdUK5GfkHxFA31x9m", 9 | "BejKhUoDTZiVfs1jqNtUSDAmGPi9xSXhVVHNStkzj4cv5GWsG8DcXUZNccYMCkpYUYSsLCTPnFE1x7zkmzGYT442SkCuPQd", 10 | "BcUQoLEptAiRwS2nWnz5f8KtDt28i3eq41ff1wXcLymX79QWQKHQKGXSeC8xh3JV59KBijGXfNDJWT752HQEU6T8REDetPZ", 11 | "BgAYtGuSVCA7riFyWwmQdz2zCvh1VocVt6an4uQSNwgSXJXrhCGqyc285gxAdF2xLJQ4sSV2drWgaY6pHKDtY8KkCGXinFZ", 12 | "Bcm6LrZtZBoiGJpVRKPTHNeZr4FLVaufScKaJ5Tp28WZSaWdRFE1bgFhZhZpY11nKr8SgDKRa33pRKb5tPMGsLk42xyx4YN" 13 | ], 14 | [ 15 | "Bhk752y7cTjjfZ3sARAkkyZTBxsJAWbeidNPodDih3dKdRAs8kYgidsfNNhULLiHJwfYVqZJtGDDuNK4tG6tfg8V7EmaURg", 16 | "BeQeMRXthNQXFgcBRUsGxZKKR2jWG7gn9SP6zzNz35kJ1VuC7fxGkKYhDWcwfpUU7gAS63KcdZiE748yS7booUndHFeR2vA", 17 | "Bc9Bvg6wtRM5pRjTocTCRXeyePhAggk4LifsqopRCEykipwzwyE94qPFbMd4n8TsyH19cjGB7TFC9Eda4wH3kboY1JZGxqz", 18 | "BewjdZNpycoXTNiAtg5NdEAyRJnnnp3ytGNbypjyb4Yd8GsrWLuSwGzgahNwDe4S4vCaD21cGfb9g3KtnwhSKnR9MJN6E51", 19 | "Bdw7pMMV64ZhHAqvtRFaXa5wqm8y8g4tRKFQCUPPGNcdYAUKScprqAFgmCXGR4kVxdcKQyVQAzGeHdqxqt3mqHgSUbTJSnL", 20 | "BePRGXi2z9dMY5dZJVQYCn75tzJ7JrjQjd3yS3EEiSd7KikTCp7gg7LdQSxvhKfmsQZZF7XJeG7JTUTVXvgeRra7EStiHaL", 21 | "Bgu7XFUTf3NhzvMKCNdW9vh8pRdGZ68t8FQjwYUu1EXL8TVqwj3PM16EtKBagwS2HLhs6Z65Ki4NYTwHAmUgW8vdU2Hs92o", 22 | "BZ4zbcn8y1g2K9Swm9vzfa3oHa9YmPu82ZzwnCb75QZWNcaR1xM5ZYTUcUjUDwpKXq7vpUyGWfdNP7MWcAkmbSF9Sf7gmTu", 23 | "Bf5KMYsScBcDM87pvgcbZnbezjPPmBKhpRU1t99wBzWwcuFcT8oJZj9VNps8Ywvd8y82xMNpbZVmiPKAKuyChsRTNnQHEGt", 24 | "BcuZ7DxxYDNdQUrWnrwQE384M94sSgCc8U4a3WivB3ZqNk8eBgormwABKrUaJ4tQBgJTfpdiAJYzcWXCbDAYS9jf4Y2zbew" 25 | ], 26 | [ 27 | "BfaTkVhk5YoeQ3tvqNhpakfjiyXnBYJZNNMdr5uvqRDWhFezWWknmn7C54omZbsPhURZCpnq9sA5AZLAMtXSiwz5MtVW67e", 28 | "BeC8Ce8uhsSdfeJqMfBCfNTGNBu2nVv65RUjtq9hNCrpMBB28hmm4AwMsW1LrAwcnobzUZHBK5Yn88bCrUserJDx977qVm7", 29 | "BgNzdhRgayTPw48xvCMNmF4bWB8BfUfQ1UyU6ZURQHLT3n35giaXK3y2N5Qf27tM5r7Ysivpi1yf3AzjixDj58rMQ2XcS1S", 30 | "Bbs9cHWzoM6ZvnU4nmQFA2aFfJRWqL4vtPBaZQ61QenQ7UqxuY6HoX3gQ1DBcwruu5EDnnJXH9hQ6ERv75KKuieu5AwPdHa", 31 | "BZy4Pg73hi4DwdsKbTQz9oAHPHywUxDFJK4LyRZbHVpZG8HWt9mCgEbHcvkUSimjuHTLxYvSfq36K4XjrAAaYCh661LdksL", 32 | "Bhsot4LAizKg7Y8wZ2WuJX8zTW1AK6zinJWgR6Sx2SKG9nnUJkzfBu4b2s6R3bjymiP5xbd7hbHXP8QBbi711KxWA16j7cP", 33 | "Bay7nanyktS5h23g4SeuujEdsWXxut7LDGDzv7e3PUfUTMmCgi6ZAWg5bDPHF8icNYPQKtgi2TejVXawasaEe5PE5UZWHSZ", 34 | "BeTUFbQPUrPKGRC9Fbjk6Ha14PaPtU5ugjBELtKhULAREH4mw4B4XbYBCtcycWqYUg8CU5Ye92xTDcc8z5XRoBA65MdVkKF", 35 | "BdvCUZocX8xPf7zMkY5c2kQ2uQm5ABeaD4oQCDTaXHLf626eA38XFkCHuocXxx3v18PbqWHkzT17cNVuWaecD6L4Tgx2pNs", 36 | "BYPumPuxceS2FAZj5bDxvqR55RneFNbT7bBiLdQLtpH1ESuFtTWgWSi8wD5En2evXdNy2VyfnmqF8XCcPmfwdrKK4Tha1M1" 37 | ], 38 | [ 39 | "BgyMm6Mxb2YARcWDvVG12h6RpTzaaADrQJyzn4P1hZ5ra6zYPy3CaE9NLmMuZdjzMYPT6bbH6vA4zZzYpK7uGQyQBUWwfir", 40 | "BgnK7oh39thSQtWXAHyj5uCeWvtEN5VW5L1QQAZJr85SMaSyUGreHmgVXmRkN4FCmuM2DvrKkrqPDAq5RMJzBuEGBpRDdwA", 41 | "BgZrRi8UGxHiLvHnBibYTDZJWs3oo4YUycoT5M9khn6xTmr4zZ7CshSe8MxYD4fA5jecR8hPWxPWEca86S1nwbsf6FEvi8Q", 42 | "Bd6xZJfxmCDCNydf8MEEs39XSvyJUoSs44at78hgSiCRLhnmsrtgfVTgTPaqaweVTH4hnV1gKnM2fXcxTx899wdk7pDKkEZ", 43 | "BajjPFr9BvxavdUhZoViMU1W6j2EVbkAC3sGyeb4w7mQLF5K3CBt7XFjaQzuzk85YxQjXn5PEk3aEFnRg3bUeoWRUMqJL1b", 44 | "BfFgXgbNm4JakCubziSwfBaRCSEUVPz3qd7TCmdPvYmt3jE8RfnrFA3YvcbQys3DncQrJREaJNw1p3ESFbQMFF8T9Wj7RYG", 45 | "Bf8cHLmgXjQMHbf17csXrDhc1Ltjd6wmUBQ6NXCVmJvCaFTM8F1WzBCftZ9D6ckekFFU4f7cRaev7C8EsuBP2dHiLFH6FxJ", 46 | "BcHrnGsjXJVCi7PpN2vcMNPq4pgExX3tcYsXvLqfm4PR5FCHxpYvuoBbAUXXxA9D9BDWSCJRAFvJMKLm3Rje7gmRKXuzCKk", 47 | "BZ56YcnPXBfNGSbEkB7QacF5SXofABVhYR5EGJFM9GeVCT5RdmX4p9shhPmTfHNmtqKcaGyPtJGg9j17qF6hsnVmVCXjSPq", 48 | "BhbkhSYb9xxS8GxiynF71X1q2KyX548ksXzJNvvrx7au8WLexBBsaMg8W79yzdqPGs6FpHAb6xTfU6PUvJxbgEHBKyASAbe" 49 | ], 50 | [ 51 | "BbpBVwUXnajCYKyTk7VYyfN8K6Roz9WHZ7zzVZXCcikd6U4iTavBLSqTkaa2aCyKHfQ2QhLRzhwX9aUynaqRUF2LEHpQ56U", 52 | "BbqwEhcSqyDPEG7mkknmasCwSepn1Fx5A2HbSWRFds5YfCgHa1eYZizMmwNrKPu5MDHN7fHnaZbyJd5qbb8yeSSTFx45U39", 53 | "BaLSP572vzmBj6XuYW8Dgghj66ktDWkejTHQQwLEFWvZJYxeENihKAi6CGJLFbuztC8dcRFjC5r3rAw9MtUebebQ4JVjNvR", 54 | "BbUhnH1aLeTR3pUGm8Ba2B4whVcBTarPkUFRNRxzPFTSRLAHKAnhjLpDHoyNgBxmmMKJDLfZsdtA6STdUXTypeZX3pJqpwX", 55 | "Ba8i7eKK8wSgZFWqSZYUGV3RTa2N5aWpJf8rQ2AFxUC77hX1tLek4315SSEovNG3KQaxzUzWWgp65gKPijDjpDk3UKMhMod", 56 | "Bgz9rAfVFdR3VkmSjbKkjdPGCgbnQwYUqQbq9ffTKFvhPN2UwtBPegvGg72ghLsgpZX5dHCCMGpUweJNBUguEo49LPJQE3b", 57 | "BgZXkTeydXDQSHePYVYKu3W1aifuX7qscTpr7Qx1t6Bj6iKmS3puFPB56r22BmYyUgVX6dprSYQMbbeNTAJfD4FT5oo1oVw", 58 | "BbTZrrWUSVECHuqmCfskFyZJ8wi1GoVDNHy8xmbqVu8aKxc3uRtiHZwUCJVfnFnpTpekoskkxkCAxfLw9dbcNawLFJxD6jr", 59 | "Bbi4mDqnUyBTKCMLWs3V8hKjbhGKGNxHzUCmAm89xDC5DwLJNE3SZ6BcatJ4cdBFNYApw7nuoU2hZi9UXngM2GXTLJpMVWd", 60 | "BfRbRVP2aZDMM9Bm9375djX1VxfTNZKDRDtoSJt4XcdYDWxLnEXxer32DDvQHCidCBYPu7qVUdTUjQ4Nc6kqJcQ4NMV6D9a" 61 | ] 62 | ] 63 | -------------------------------------------------------------------------------- /utils/walletdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import argparse 3 | import logging 4 | import operator 5 | import re 6 | 7 | from monero import exceptions 8 | from monero.backends.jsonrpc import JSONRPCWallet, RPCError 9 | from monero.wallet import Wallet 10 | 11 | 12 | def url_data(url): 13 | gs = ( 14 | re.compile( 15 | r"^(?:(?P[a-z0-9_-]+)?(?::(?P[^@]+))?@)?(?P[^:\s]+)(?::(?P[0-9]+))?$" 16 | ) 17 | .match(url) 18 | .groupdict() 19 | ) 20 | return dict(filter(operator.itemgetter(1), gs.items())) 21 | 22 | 23 | def get_wallet(): 24 | argsparser = argparse.ArgumentParser(description="Display wallet contents") 25 | argsparser.add_argument( 26 | "wallet_rpc_url", 27 | nargs="?", 28 | type=url_data, 29 | default="127.0.0.1:18082", 30 | help="Wallet RPC URL [user[:password]@]host[:port]", 31 | ) 32 | argsparser.add_argument( 33 | "-v", 34 | dest="verbosity", 35 | action="count", 36 | default=0, 37 | help="Verbosity (repeat to increase; -v for INFO, -vv for DEBUG", 38 | ) 39 | argsparser.add_argument( 40 | "-t", dest="timeout", type=int, default=30, help="Request timeout" 41 | ) 42 | args = argsparser.parse_args() 43 | level = logging.WARNING 44 | if args.verbosity == 1: 45 | level = logging.INFO 46 | elif args.verbosity > 1: 47 | level = logging.DEBUG 48 | logging.basicConfig(level=level, format="%(asctime)-15s %(message)s") 49 | return Wallet(JSONRPCWallet(timeout=args.timeout, **args.wallet_rpc_url)) 50 | 51 | 52 | _TXHDR = ( 53 | "timestamp height id/hash " 54 | " amount fee {dir:95s} payment_id" 55 | ) 56 | 57 | 58 | def pmt2str(pmt): 59 | res = [ 60 | "{time} {height:7d} {hash} {amount:17.12f} {fee:13.12f} {addr} {payment_id}".format( 61 | time=pmt.timestamp.strftime("%d-%m-%y %H:%M:%S") 62 | if getattr(pmt, "timestamp", None) 63 | else None, 64 | height=pmt.transaction.height or 0, 65 | hash=pmt.transaction.hash, 66 | amount=pmt.amount, 67 | fee=pmt.transaction.fee or 0, 68 | payment_id=pmt.payment_id, 69 | addr=getattr(pmt, "local_address", None) or "", 70 | ) 71 | ] 72 | try: 73 | for dest in pmt.destinations: 74 | res.append( 75 | " {amount:17.12f} to {address}".format( 76 | address=dest[0], amount=dest[1] 77 | ) 78 | ) 79 | except AttributeError: 80 | pass 81 | return "\n".join(res) 82 | 83 | 84 | def a2str(a): 85 | return "{addr} {label}".format(addr=a, label=a.label or "") 86 | 87 | 88 | w = get_wallet() 89 | masteraddr = w.address() 90 | print( 91 | "Master address: {addr}\n" 92 | "Balance: {total:16.12f} ({unlocked:16.12f} unlocked)".format( 93 | addr=a2str(masteraddr), total=w.balance(), unlocked=w.balance(unlocked=True) 94 | ) 95 | ) 96 | try: 97 | seed = w.seed() 98 | except ( 99 | exceptions.WalletIsNotDeterministic, 100 | RPCError, 101 | ): # FIXME: Remove RPCError once PR#4563 is merged in monero 102 | seed = "[--- wallet is not deterministic and has no seed ---]" 103 | print( 104 | "Keys:\n" 105 | " private spend: {ssk}\n" 106 | " private view: {svk}\n" 107 | " public spend: {psk}\n" 108 | " public view: {pvk}\n\n" 109 | "Seed:\n{seed}".format( 110 | ssk=w.spend_key(), 111 | svk=w.view_key(), 112 | psk=masteraddr.spend_key(), 113 | pvk=masteraddr.view_key(), 114 | seed=seed, 115 | ) 116 | ) 117 | 118 | if len(w.accounts) > 1: 119 | print("\nWallet has {num} account(s):".format(num=len(w.accounts))) 120 | for acc in w.accounts: 121 | print("\nAccount {idx:02d}:".format(idx=acc.index)) 122 | print( 123 | "Balance: {total:16.12f} ({unlocked:16.12f} unlocked)".format( 124 | total=acc.balance(), unlocked=acc.balance(unlocked=True) 125 | ) 126 | ) 127 | addresses = acc.addresses() 128 | print("{num:2d} address(es):".format(num=len(addresses))) 129 | print("\n".join(map(a2str, addresses))) 130 | ins = acc.incoming(unconfirmed=True) 131 | if ins: 132 | print("\nIncoming transactions:") 133 | print(_TXHDR.format(dir="received by")) 134 | for tx in ins: 135 | print(pmt2str(tx)) 136 | outs = acc.outgoing(unconfirmed=True) 137 | if outs: 138 | print("\nOutgoing transactions:") 139 | print(_TXHDR.format(dir="sent from")) 140 | for tx in outs: 141 | print(pmt2str(tx)) 142 | else: 143 | addresses = w.accounts[0].addresses() 144 | print("{num:2d} address(es):".format(num=len(addresses))) 145 | print("\n".join(map(a2str, addresses))) 146 | ins = w.incoming(unconfirmed=True) 147 | if ins: 148 | print("\nIncoming transactions:") 149 | print(_TXHDR.format(dir="received by")) 150 | for tx in ins: 151 | print(pmt2str(tx)) 152 | outs = w.outgoing(unconfirmed=True) 153 | if outs: 154 | print("\nOutgoing transactions:") 155 | print(_TXHDR.format(dir="sent from")) 156 | for tx in outs: 157 | print(pmt2str(tx)) 158 | -------------------------------------------------------------------------------- /tests/data/test_jsonrpcdaemon/test_get_block_2288515.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 0, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "blob": "0e0efef0e880066d78ace422007a2cefb423553b39f8b58f6067698eac8162b25732d0b99988d37dc2002302bfd78b0101ff83d78b0101d58aa8aaa02202e90b5189598861b8dc3250b05847efd4ddb31d33b667037ae75572f4fef176c43401d3edb9548f1f68cb3de38f597e087e7514e8533ad2911480c4afffde217cb0dc02110000041b31710e950000000000000000000011b6b1fe267ec2a0f4c6937ebbe2e94969341d293293f148b2fe4e6f1c618b57a86c769da02b54ecac61d85f19b359401dd5595eebaaf488875e8165542e9eef522072a2929728e4e316744588f108b258c449072c19656b57ce1b9ba1a34e534d95619c252275642a394396ece0e218f6f4003e58bf307a02e03f09a59d53e5637dbb101aa4d3c6c58b484f7e580d89b52d70b0a9e67471e64ada3930bdae66c8bb4531547be88cd8601f8490688b5461889c4ca78977000844e015aac51698cf922df5547d43de3e260cb9a87342498fffa89f3cc31564b6a41cef61320b85695d454ac941ac2f7ca00e673a019066e7874b9d1f9055f0d6c7b6c47bc3efc52de13150199cb907b06d0c5fe009b64258662e29bdcc58f53d88be4cb044a50352f9c8c3549e9df079cd0b75f87937152ec5bdb9c8b68695ec80a129f9770ad80b05dbd5c025270fc38366f06d037a43ee0db46892ac471a61328d9733d197c6c1ecfdf344bac1a2795a179e10d15cffe66701c3f53d9410b2f63998a1fbab3332c471f2f16e24ab9475d0cd656aa49a0bce7fb38f82795ce6935b4602b03f0d0cddba5bd9abcb5ae50698e5f735e31ae2c10a673b763434594beea09a5990c02f3e82c4eafb438223df4022cd146bfcadd8e3c1da70b8113c626a7bc4d8780ca6009a0f3043c1eccf3519e26137a618a446844105f0e3a87f60104a4c2244212aed1c72b4f4b442d4f4b562c153b438711ae9069aa2bdf3426a4209ba91db2dec", 6 | "block_header": { 7 | "block_size": 33101, 8 | "block_weight": 33101, 9 | "cumulative_difficulty": 86655943855900269, 10 | "cumulative_difficulty_top64": 0, 11 | "depth": 13, 12 | "difficulty": 239419211907, 13 | "difficulty_top64": 0, 14 | "hash": "1c68300646dda11a89cc9ca4001100745fcbd192e0e6efb6b06bd4d25851662b", 15 | "height": 2288515, 16 | "long_term_weight": 33101, 17 | "major_version": 14, 18 | "miner_tx_hash": "e31c38097769b1c49d31e7b041823da00915cdba77b9794d63ccb3d6571f2565", 19 | "minor_version": 14, 20 | "nonce": 587252349, 21 | "num_txes": 17, 22 | "orphan_status": false, 23 | "pow_hash": "", 24 | "prev_hash": "6d78ace422007a2cefb423553b39f8b58f6067698eac8162b25732d0b99988d3", 25 | "reward": 1176909776213, 26 | "timestamp": 1612331134, 27 | "wide_cumulative_difficulty": "0x133dd1f2f35066d", 28 | "wide_difficulty": "0x37be7f4083" 29 | }, 30 | "credits": 0, 31 | "json": "{\n \"major_version\": 14, \n \"minor_version\": 14, \n \"timestamp\": 1612331134, \n \"prev_id\": \"6d78ace422007a2cefb423553b39f8b58f6067698eac8162b25732d0b99988d3\", \n \"nonce\": 587252349, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 2288575, \n \"vin\": [ {\n \"gen\": {\n \"height\": 2288515\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 1176909776213, \n \"target\": {\n \"key\": \"e90b5189598861b8dc3250b05847efd4ddb31d33b667037ae75572f4fef176c4\"\n }\n }\n ], \n \"extra\": [ 1, 211, 237, 185, 84, 143, 31, 104, 203, 61, 227, 143, 89, 126, 8, 126, 117, 20, 232, 83, 58, 210, 145, 20, 128, 196, 175, 255, 222, 33, 124, 176, 220, 2, 17, 0, 0, 4, 27, 49, 113, 14, 149, 0, 0, 0, 0, 0, 0, 0, 0, 0\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ \"b6b1fe267ec2a0f4c6937ebbe2e94969341d293293f148b2fe4e6f1c618b57a8\", \"6c769da02b54ecac61d85f19b359401dd5595eebaaf488875e8165542e9eef52\", \"2072a2929728e4e316744588f108b258c449072c19656b57ce1b9ba1a34e534d\", \"95619c252275642a394396ece0e218f6f4003e58bf307a02e03f09a59d53e563\", \"7dbb101aa4d3c6c58b484f7e580d89b52d70b0a9e67471e64ada3930bdae66c8\", \"bb4531547be88cd8601f8490688b5461889c4ca78977000844e015aac51698cf\", \"922df5547d43de3e260cb9a87342498fffa89f3cc31564b6a41cef61320b8569\", \"5d454ac941ac2f7ca00e673a019066e7874b9d1f9055f0d6c7b6c47bc3efc52d\", \"e13150199cb907b06d0c5fe009b64258662e29bdcc58f53d88be4cb044a50352\", \"f9c8c3549e9df079cd0b75f87937152ec5bdb9c8b68695ec80a129f9770ad80b\", \"05dbd5c025270fc38366f06d037a43ee0db46892ac471a61328d9733d197c6c1\", \"ecfdf344bac1a2795a179e10d15cffe66701c3f53d9410b2f63998a1fbab3332\", \"c471f2f16e24ab9475d0cd656aa49a0bce7fb38f82795ce6935b4602b03f0d0c\", \"ddba5bd9abcb5ae50698e5f735e31ae2c10a673b763434594beea09a5990c02f\", \"3e82c4eafb438223df4022cd146bfcadd8e3c1da70b8113c626a7bc4d8780ca6\", \"009a0f3043c1eccf3519e26137a618a446844105f0e3a87f60104a4c2244212a\", \"ed1c72b4f4b442d4f4b562c153b438711ae9069aa2bdf3426a4209ba91db2dec\"\n ]\n}", 32 | "miner_tx_hash": "e31c38097769b1c49d31e7b041823da00915cdba77b9794d63ccb3d6571f2565", 33 | "status": "OK", 34 | "top_hash": "", 35 | "tx_hashes": [ 36 | "b6b1fe267ec2a0f4c6937ebbe2e94969341d293293f148b2fe4e6f1c618b57a8", 37 | "6c769da02b54ecac61d85f19b359401dd5595eebaaf488875e8165542e9eef52", 38 | "2072a2929728e4e316744588f108b258c449072c19656b57ce1b9ba1a34e534d", 39 | "95619c252275642a394396ece0e218f6f4003e58bf307a02e03f09a59d53e563", 40 | "7dbb101aa4d3c6c58b484f7e580d89b52d70b0a9e67471e64ada3930bdae66c8", 41 | "bb4531547be88cd8601f8490688b5461889c4ca78977000844e015aac51698cf", 42 | "922df5547d43de3e260cb9a87342498fffa89f3cc31564b6a41cef61320b8569", 43 | "5d454ac941ac2f7ca00e673a019066e7874b9d1f9055f0d6c7b6c47bc3efc52d", 44 | "e13150199cb907b06d0c5fe009b64258662e29bdcc58f53d88be4cb044a50352", 45 | "f9c8c3549e9df079cd0b75f87937152ec5bdb9c8b68695ec80a129f9770ad80b", 46 | "05dbd5c025270fc38366f06d037a43ee0db46892ac471a61328d9733d197c6c1", 47 | "ecfdf344bac1a2795a179e10d15cffe66701c3f53d9410b2f63998a1fbab3332", 48 | "c471f2f16e24ab9475d0cd656aa49a0bce7fb38f82795ce6935b4602b03f0d0c", 49 | "ddba5bd9abcb5ae50698e5f735e31ae2c10a673b763434594beea09a5990c02f", 50 | "3e82c4eafb438223df4022cd146bfcadd8e3c1da70b8113c626a7bc4d8780ca6", 51 | "009a0f3043c1eccf3519e26137a618a446844105f0e3a87f60104a4c2244212a", 52 | "ed1c72b4f4b442d4f4b562c153b438711ae9069aa2bdf3426a4209ba91db2dec" 53 | ], 54 | "untrusted": false 55 | } 56 | } -------------------------------------------------------------------------------- /monero/base58.py: -------------------------------------------------------------------------------- 1 | # MoneroPy - A python toolbox for Monero 2 | # Copyright (C) 2016 The MoneroPy Developers. 3 | # 4 | # MoneroPy is released under the BSD 3-Clause license. Use and redistribution of 5 | # this software is subject to the license terms in the LICENSE file found in the 6 | # top-level directory of this distribution. 7 | # 8 | # Modified by emesik and rooterkyberian: 9 | # + optimized 10 | # + proper exceptions instead of returning errors as results 11 | 12 | __alphabet = [ 13 | ord(s) for s in "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 14 | ] 15 | __b58base = 58 16 | __UINT64MAX = 2 ** 64 17 | __encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11] 18 | __fullBlockSize = 8 19 | __fullEncodedBlockSize = 11 20 | 21 | 22 | def _hexToBin(hex_): 23 | if len(hex_) % 2 != 0: 24 | raise ValueError("Hex string has invalid length: %d" % len(hex_)) 25 | return [int(hex_[i : i + 2], 16) for i in range(0, len(hex_), 2)] 26 | 27 | 28 | def _binToHex(bin_): 29 | return "".join("%02x" % int(b) for b in bin_) 30 | 31 | 32 | def _uint8be_to_64(data): 33 | if not (1 <= len(data) <= 8): 34 | raise ValueError("Invalid input length: %d" % len(data)) 35 | 36 | res = 0 37 | for b in data: 38 | res = res << 8 | b 39 | return res 40 | 41 | 42 | def _uint64_to_8be(num, size): 43 | if size < 1 or size > 8: 44 | raise ValueError("Invalid input length: %d" % size) 45 | res = [0] * size 46 | 47 | twopow8 = 2 ** 8 48 | for i in range(size - 1, -1, -1): 49 | res[i] = num % twopow8 50 | num = num // twopow8 51 | 52 | return res 53 | 54 | 55 | def encode_block(data, buf, index): 56 | l_data = len(data) 57 | 58 | if l_data < 1 or l_data > __fullEncodedBlockSize: 59 | raise ValueError("Invalid block length: %d" % l_data) 60 | 61 | num = _uint8be_to_64(data) 62 | i = __encodedBlockSizes[l_data] - 1 63 | 64 | while num > 0: 65 | remainder = num % __b58base 66 | num = num // __b58base 67 | buf[index + i] = __alphabet[remainder] 68 | i -= 1 69 | 70 | return buf 71 | 72 | 73 | def encode(hex): 74 | """Encode hexadecimal string as base58 (ex: encoding a Monero address).""" 75 | data = _hexToBin(hex) 76 | l_data = len(data) 77 | 78 | if l_data == 0: 79 | return "" 80 | 81 | full_block_count = l_data // __fullBlockSize 82 | last_block_size = l_data % __fullBlockSize 83 | res_size = ( 84 | full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size] 85 | ) 86 | 87 | res = bytearray([__alphabet[0]] * res_size) 88 | 89 | for i in range(full_block_count): 90 | res = encode_block( 91 | data[(i * __fullBlockSize) : (i * __fullBlockSize + __fullBlockSize)], 92 | res, 93 | i * __fullEncodedBlockSize, 94 | ) 95 | 96 | if last_block_size > 0: 97 | res = encode_block( 98 | data[ 99 | (full_block_count * __fullBlockSize) : ( 100 | full_block_count * __fullBlockSize + last_block_size 101 | ) 102 | ], 103 | res, 104 | full_block_count * __fullEncodedBlockSize, 105 | ) 106 | 107 | return bytes(res).decode("ascii") 108 | 109 | 110 | def decode_block(data, buf, index): 111 | l_data = len(data) 112 | 113 | if l_data < 1 or l_data > __fullEncodedBlockSize: 114 | raise ValueError("Invalid block length: %d" % l_data) 115 | 116 | res_size = __encodedBlockSizes.index(l_data) 117 | if res_size <= 0: 118 | raise ValueError("Invalid block size: %d" % res_size) 119 | 120 | res_num = 0 121 | order = 1 122 | for i in range(l_data - 1, -1, -1): 123 | digit = __alphabet.index(data[i]) 124 | if digit < 0: 125 | raise ValueError("Invalid symbol: %s" % data[i]) 126 | 127 | product = order * digit + res_num 128 | if product > __UINT64MAX: 129 | raise ValueError( 130 | "Overflow: %d * %d + %d = %d" % (order, digit, res_num, product) 131 | ) 132 | 133 | res_num = product 134 | order = order * __b58base 135 | 136 | if res_size < __fullBlockSize and 2 ** (8 * res_size) <= res_num: 137 | raise ValueError("Overflow: %d doesn't fit in %d bit(s)" % (res_num, res_size)) 138 | 139 | tmp_buf = _uint64_to_8be(res_num, res_size) 140 | buf[index : index + len(tmp_buf)] = tmp_buf 141 | 142 | return buf 143 | 144 | 145 | def decode(enc): 146 | """Decode a base58 string (ex: a Monero address) into hexidecimal form.""" 147 | enc = bytearray(enc, encoding="ascii") 148 | l_enc = len(enc) 149 | 150 | if l_enc == 0: 151 | return "" 152 | 153 | full_block_count = l_enc // __fullEncodedBlockSize 154 | last_block_size = l_enc % __fullEncodedBlockSize 155 | try: 156 | last_block_decoded_size = __encodedBlockSizes.index(last_block_size) 157 | except ValueError: 158 | raise ValueError("Invalid encoded length: %d" % l_enc) 159 | 160 | data_size = full_block_count * __fullBlockSize + last_block_decoded_size 161 | 162 | data = bytearray(data_size) 163 | for i in range(full_block_count): 164 | data = decode_block( 165 | enc[ 166 | (i * __fullEncodedBlockSize) : ( 167 | i * __fullEncodedBlockSize + __fullEncodedBlockSize 168 | ) 169 | ], 170 | data, 171 | i * __fullBlockSize, 172 | ) 173 | 174 | if last_block_size > 0: 175 | data = decode_block( 176 | enc[ 177 | (full_block_count * __fullEncodedBlockSize) : ( 178 | full_block_count * __fullEncodedBlockSize + last_block_size 179 | ) 180 | ], 181 | data, 182 | full_block_count * __fullBlockSize, 183 | ) 184 | 185 | return _binToHex(data) 186 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Monero Python module documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jan 16 21:37:52 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | 23 | sys.path.insert(0, os.path.abspath("../..")) 24 | 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = ["sphinx.ext.autodoc"] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = ".rst" 45 | 46 | # The master toctree document. 47 | master_doc = "index" 48 | 49 | # General information about the project. 50 | project = "Monero Python module" 51 | copyright = "2018, Michal Salaban" 52 | author = "Michal Salaban" 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = "1.1.1" 60 | # The full version, including alpha/beta/rc tags. 61 | release = "1.1.1" 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = [] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = "sphinx" 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ---------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = "alabaster" 88 | 89 | # Theme options are theme-specific and customize the look and feel of a theme 90 | # further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ["_static"] 99 | 100 | # Custom sidebar templates, must be a dictionary that maps document names 101 | # to template names. 102 | # 103 | # This is required for the alabaster theme 104 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 105 | html_sidebars = { 106 | "index": [ 107 | "relations.html", # needs 'show_related': True theme option to display 108 | "searchbox.html", 109 | ], 110 | "**": ["searchbox.html", "globaltoc.html"], 111 | } 112 | 113 | 114 | # -- Options for HTMLHelp output ------------------------------------------ 115 | 116 | # Output file base name for HTML help builder. 117 | htmlhelp_basename = "MoneroPythonmoduledoc" 118 | 119 | 120 | # -- Options for LaTeX output --------------------------------------------- 121 | 122 | latex_elements = { 123 | # The paper size ('letterpaper' or 'a4paper'). 124 | # 125 | # 'papersize': 'letterpaper', 126 | # The font size ('10pt', '11pt' or '12pt'). 127 | # 128 | # 'pointsize': '10pt', 129 | # Additional stuff for the LaTeX preamble. 130 | # 131 | # 'preamble': '', 132 | # Latex figure (float) alignment 133 | # 134 | # 'figure_align': 'htbp', 135 | } 136 | 137 | # Grouping the document tree into LaTeX files. List of tuples 138 | # (source start file, target name, title, 139 | # author, documentclass [howto, manual, or own class]). 140 | latex_documents = [ 141 | ( 142 | master_doc, 143 | "MoneroPythonmodule.tex", 144 | "Monero Python module Documentation", 145 | "Michal Salaban", 146 | "manual", 147 | ), 148 | ] 149 | 150 | 151 | # -- Options for manual page output --------------------------------------- 152 | 153 | # One entry per manual page. List of tuples 154 | # (source start file, name, description, authors, manual section). 155 | man_pages = [ 156 | ( 157 | master_doc, 158 | "moneropythonmodule", 159 | "Monero Python module Documentation", 160 | [author], 161 | 1, 162 | ) 163 | ] 164 | 165 | 166 | # -- Options for Texinfo output ------------------------------------------- 167 | 168 | # Grouping the document tree into Texinfo files. List of tuples 169 | # (source start file, target name, title, author, 170 | # dir menu entry, description, category) 171 | texinfo_documents = [ 172 | ( 173 | master_doc, 174 | "MoneroPythonmodule", 175 | "Monero Python module Documentation", 176 | author, 177 | "MoneroPythonmodule", 178 | "One line description of project.", 179 | "Miscellaneous", 180 | ), 181 | ] 182 | --------------------------------------------------------------------------------