monero 0.0.0.8 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +13 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -100
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/monero.rb +37 -13
- data/lib/monero_rpc/client.rb +49 -0
- data/lib/monero_rpc/config.rb +13 -0
- data/lib/{rpc → monero_rpc}/payment.rb +1 -3
- data/lib/{rpc → monero_rpc}/transfer.rb +19 -24
- data/lib/monero_rpc/transfer_class.rb +30 -0
- data/lib/monero_rpc/version.rb +3 -0
- data/lib/monero_rpc/wallet.rb +157 -0
- data/monero.gemspec +3 -3
- data/spec/monero_spec.rb +109 -0
- data/spec/spec_helper.rb +20 -0
- metadata +29 -18
- data/lib/rpc/client.rb +0 -40
- data/lib/rpc/config.rb +0 -8
- data/lib/rpc/incoming_transfer.rb +0 -26
- data/lib/rpc/version.rb +0 -3
- data/lib/rpc/wallet.rb +0 -148
@@ -0,0 +1,49 @@
|
|
1
|
+
class MoneroRPC::Client
|
2
|
+
include MoneroRPC::Wallet
|
3
|
+
include MoneroRPC::Transfer
|
4
|
+
|
5
|
+
attr_accessor :host, :port, :username, :password, :debug, :in_transfer_clazz, :out_transfer_clazz
|
6
|
+
|
7
|
+
def initialize(args= {})
|
8
|
+
self.host = args.fetch(:host, MoneroRPC.config.host)
|
9
|
+
self.port = args.fetch(:port, MoneroRPC.config.port)
|
10
|
+
self.username = args.fetch(:username, MoneroRPC.config.username)
|
11
|
+
self.password = args.fetch(:password, MoneroRPC.config.password)
|
12
|
+
self.debug = args.fetch(:debug, MoneroRPC.config.debug)
|
13
|
+
self.in_transfer_clazz = args.fetch(:in_transfer_clazz, MoneroRPC.config.in_transfer_clazz || "MoneroRPC::IncomingTransfer")
|
14
|
+
self.out_transfer_clazz = args.fetch(:out_transfer_clazz, MoneroRPC.config.out_transfer_clazz || "MoneroRPC::OutgoingTransfer")
|
15
|
+
end
|
16
|
+
|
17
|
+
def close!
|
18
|
+
request("stop_wallet")
|
19
|
+
end
|
20
|
+
|
21
|
+
def request(method, params="")
|
22
|
+
# TODO
|
23
|
+
# who can implement digest auth with net/http?
|
24
|
+
# this is really ugly!
|
25
|
+
data = '{"jsonrpc":"2.0","id":"0","method": "'+method+'", "params": '+params.to_json+' }'
|
26
|
+
args = ""
|
27
|
+
args << " -s"
|
28
|
+
args << " -u #{username}:#{password} --digest"
|
29
|
+
args << " -X POST #{base_uri}/json_rpc"
|
30
|
+
args << " -d '#{data}'"
|
31
|
+
args << " -H 'Content-Type: application/json'"
|
32
|
+
|
33
|
+
p "curl #{args}" if debug
|
34
|
+
json = JSON.parse(`curl #{args}`)
|
35
|
+
|
36
|
+
# Error handling
|
37
|
+
if json["error"]
|
38
|
+
raise "#{json["error"]["message"]} | code: #{json["error"]["code"]}"
|
39
|
+
end
|
40
|
+
json["result"]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def base_uri
|
46
|
+
URI.parse("http://#{host}:#{port}")
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
module MoneroRPC
|
3
|
+
class Config
|
4
|
+
include Singleton
|
5
|
+
attr_accessor :host, :port, :username, :password, :debug, :in_transfer_clazz, :out_transfer_clazz
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.in_transfer_clazz = "MoneroRPC::IncomingTransfer"
|
9
|
+
self.out_transfer_clazz = "MoneroRPC::OutgoingTransfer"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -22,34 +22,29 @@
|
|
22
22
|
# get_tx_hex - boolean; Return the transaction as hex string after sending
|
23
23
|
|
24
24
|
|
25
|
-
module
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def self.create(address, amount, args={})
|
31
|
-
send_bulk([address: address, amount: amount], args)
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.send_bulk(destinations, args={})
|
25
|
+
module MoneroRPC::Transfer
|
26
|
+
def create_transfer(address, amount, args={})
|
27
|
+
send_bulk_transfer([address: address, amount: amount], args)
|
28
|
+
end
|
35
29
|
|
36
|
-
|
37
|
-
fee = args.fetch(:fee, 1) # ignored anyways
|
38
|
-
unlock_time = args.fetch(:unlock_time, 0)
|
39
|
-
payment_id = args.fetch(:payment_id, nil)
|
40
|
-
get_tx_key = args.fetch(:get_tx_key, true)
|
41
|
-
priority = args.fetch(:priority, 0)
|
42
|
-
do_not_relay = args.fetch(:do_not_relay, false)
|
43
|
-
get_tx_hex = args.fetch(:get_tx_hex, false)
|
30
|
+
def send_bulk_transfer(destinations, args={})
|
44
31
|
|
32
|
+
mixin = args.fetch(:mixin, 7)
|
33
|
+
fee = args.fetch(:fee, 1) # ignored anyways
|
34
|
+
unlock_time = args.fetch(:unlock_time, 0)
|
35
|
+
payment_id = args.fetch(:payment_id, nil)
|
36
|
+
get_tx_key = args.fetch(:get_tx_key, true)
|
37
|
+
priority = args.fetch(:priority, 0)
|
38
|
+
do_not_relay = args.fetch(:do_not_relay, false)
|
39
|
+
get_tx_hex = args.fetch(:get_tx_hex, false)
|
45
40
|
|
46
|
-
options = {
|
47
|
-
destinations: destinations, fee: fee, mixin: mixin, unlock_time: unlock_time,
|
48
|
-
payment_id: payment_id, get_tx_key: get_tx_key, priority: priority, do_not_relay: do_not_relay, get_tx_hex: get_tx_hex
|
49
|
-
}
|
50
41
|
|
51
|
-
|
52
|
-
|
42
|
+
options = {
|
43
|
+
destinations: destinations, fee: fee, mixin: mixin, unlock_time: unlock_time,
|
44
|
+
payment_id: payment_id, get_tx_key: get_tx_key, priority: priority, do_not_relay: do_not_relay, get_tx_hex: get_tx_hex
|
45
|
+
}
|
53
46
|
|
47
|
+
request("transfer", options)
|
54
48
|
end
|
49
|
+
|
55
50
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MoneroRPC
|
2
|
+
class TransferClass
|
3
|
+
|
4
|
+
attr_accessor :address, :amount, :double_spend_seen, :fee, :height, :note,
|
5
|
+
:payment_id, :subaddr_index, :timestamp, :txid, :type, :unlock_time,
|
6
|
+
:destinations, :confirmations, :suggested_confirmations_threshold, :subaddr_indices
|
7
|
+
|
8
|
+
def initialize(args={})
|
9
|
+
args.each do |k,v|
|
10
|
+
self.send("#{k}=", v)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def confirmed?
|
15
|
+
confirmations >= 10
|
16
|
+
end
|
17
|
+
|
18
|
+
def pending?
|
19
|
+
height == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def locked?
|
23
|
+
raise "TODO"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class MoneroRPC::IncomingTransfer < MoneroRPC::TransferClass; end
|
30
|
+
class MoneroRPC::OutgoingTransfer < MoneroRPC::TransferClass; end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module MoneroRPC::Wallet
|
2
|
+
|
3
|
+
def create_address(label="")
|
4
|
+
request("create_address", label: label)
|
5
|
+
end
|
6
|
+
|
7
|
+
def get_address
|
8
|
+
get_addresses(0, [0]).first["address"]
|
9
|
+
end
|
10
|
+
alias_method :address, :get_address
|
11
|
+
|
12
|
+
def get_addresses(account_index=0, address_index=[0])
|
13
|
+
request("get_address", {account_index: account_index, address_index: address_index})["addresses"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def getbalance
|
17
|
+
request("getbalance")
|
18
|
+
end
|
19
|
+
|
20
|
+
def balance
|
21
|
+
XMR.new(getbalance["balance"])
|
22
|
+
end
|
23
|
+
|
24
|
+
def unlocked_balance
|
25
|
+
XMR.new(getbalance["unlocked_balance"])
|
26
|
+
end
|
27
|
+
|
28
|
+
def getaddress
|
29
|
+
request("getaddress")["address"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def getheight
|
33
|
+
request("getheight")["height"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def query_key(type)
|
37
|
+
raise ArgumentError unless ["mnemonic", "view_key"].include?(type.to_s)
|
38
|
+
request("query_key", {key_type: type})["key"]
|
39
|
+
end
|
40
|
+
def view_key; query_key(:view_key); end
|
41
|
+
def mnemonic_seed; query_key(:mnemonic); end
|
42
|
+
|
43
|
+
|
44
|
+
def make_integrated_address(payment_id = "")
|
45
|
+
# TODO
|
46
|
+
# Check if payment_id is correct format
|
47
|
+
request("make_integrated_address", {payment_id: payment_id})
|
48
|
+
end
|
49
|
+
|
50
|
+
def split_integrated_address(address)
|
51
|
+
request("split_integrated_address", {integrated_address: address})
|
52
|
+
end
|
53
|
+
|
54
|
+
def incoming_transfers(type)
|
55
|
+
raise ArgumentError unless ["all", "available", "unavailable"].include?(type.to_s)
|
56
|
+
json = request("incoming_transfers", {transfer_type: type})
|
57
|
+
json["transfers"] || []
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_payments(payment_id)
|
61
|
+
payments = request("get_payments", {payment_id: payment_id})["payments"] || []
|
62
|
+
# TODO
|
63
|
+
# make it a MoneroRPC::Payment that hase a amount as XMR and confirmations (getheight - tx.block_height)
|
64
|
+
payments.map{|x| Payment.from_raw(x) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_bulk_payments(payment_ids, min_block_height)
|
68
|
+
payments = request("get_bulk_payments", {"payment_ids": payment_ids, "min_block_height": min_block_height})
|
69
|
+
return payments
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# in - boolean;
|
74
|
+
# out - boolean;
|
75
|
+
# pending - boolean;
|
76
|
+
# failed - boolean;
|
77
|
+
# pool - boolean;
|
78
|
+
# filter_by_height - boolean;
|
79
|
+
# min_height - unsigned int;
|
80
|
+
# max_height - unsigned int;
|
81
|
+
def get_transfers(args={})
|
82
|
+
f_in = args.fetch(:in, true)
|
83
|
+
out = args.fetch(:out, false)
|
84
|
+
pending = args.fetch(:pending, true)
|
85
|
+
failed = args.fetch(:failed, false)
|
86
|
+
pool = args.fetch(:pool, true)
|
87
|
+
filter_by_height = args.fetch(:filter_by_height, false)
|
88
|
+
min_height = args.fetch(:min_height, 0)
|
89
|
+
max_height = args.fetch(:max_height, 0)
|
90
|
+
|
91
|
+
options = {in: f_in, out: out, pending: pending, failed: failed, pool: pool, filter_by_height: filter_by_height, min_height: min_height}
|
92
|
+
options[:max_height] = max_height if max_height > min_height
|
93
|
+
|
94
|
+
h = Hash.new
|
95
|
+
json = request("get_transfers", options)
|
96
|
+
json.map{|k, v|
|
97
|
+
h[k] = v.collect{|transfer|
|
98
|
+
if k == "in"
|
99
|
+
in_transfer_clazz.constantize.new(transfer)
|
100
|
+
else
|
101
|
+
out_transfer_clazz.constantize.new(transfer)
|
102
|
+
end
|
103
|
+
}
|
104
|
+
}
|
105
|
+
return h
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_all_incoming_transfers(args={})
|
109
|
+
pending = args.fetch(:pending, true)
|
110
|
+
min_height = args.fetch(:min_height, 0)
|
111
|
+
max_height = args.fetch(:max_height, 0)
|
112
|
+
|
113
|
+
all = get_transfers(filter_by_height: true, min_height: min_height, max_height: max_height, in: true, out: false, pending: true, pool: true)
|
114
|
+
[ all["in"], all["pending"], all["pool"]].flatten.compact
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_all_outgoing_transfers(args={})
|
118
|
+
pending = args.fetch(:pending, true)
|
119
|
+
min_height = args.fetch(:min_height, 0)
|
120
|
+
max_height = args.fetch(:max_height, 0)
|
121
|
+
|
122
|
+
all = get_transfers(filter_by_height: true, min_height: min_height, max_height: max_height, in: false, out: true, pending: pending, pool: true)
|
123
|
+
[ all["out"], all["pending"], all["pool"]].flatten.compact
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_transfer_by_txid(txid)
|
127
|
+
request("get_transfer_by_txid", {txid: txid })
|
128
|
+
end
|
129
|
+
|
130
|
+
# creates a wallet and uses it
|
131
|
+
# if wallet exists, will automatically uses it!
|
132
|
+
def create_wallet(filename, password, language="English")
|
133
|
+
# TODO
|
134
|
+
# language correct format?
|
135
|
+
options = { filename: filename, password: password, language: language }
|
136
|
+
!! request("create_wallet", options)
|
137
|
+
end
|
138
|
+
alias_method :create, :create_wallet
|
139
|
+
|
140
|
+
# returns current balance if open successfull
|
141
|
+
def open_wallet(filename, password="")
|
142
|
+
options = { filename: filename, password: password}
|
143
|
+
if request("open_wallet", options)
|
144
|
+
balance
|
145
|
+
else
|
146
|
+
false
|
147
|
+
end
|
148
|
+
end
|
149
|
+
alias_method :open, :open_wallet
|
150
|
+
|
151
|
+
# stops current MoneroRPC process!
|
152
|
+
def stop_wallet
|
153
|
+
close!
|
154
|
+
end
|
155
|
+
alias_method :close, :stop_wallet
|
156
|
+
|
157
|
+
end
|
data/monero.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require '
|
4
|
+
require 'monero_rpc/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "monero"
|
8
|
-
spec.version =
|
8
|
+
spec.version = MoneroRPC::VERSION
|
9
9
|
spec.authors = ["Tim Kretschmer"]
|
10
10
|
spec.email = ["tim@krtschmr.de"]
|
11
11
|
spec.description = %q{A Monero-Wallet-RPC ruby client}
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
24
24
|
|
25
25
|
spec.required_rubygems_version = '>= 1.3.6'
|
26
26
|
|
data/spec/monero_spec.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe MoneroRPC do
|
4
|
+
let(:monero_rpc) { MoneroRPC.new }
|
5
|
+
|
6
|
+
it "has a version number" do
|
7
|
+
expect(MoneroRPC::VERSION).not_to be nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "gets the current address" do
|
11
|
+
address = monero_rpc.address
|
12
|
+
expect(address.length).to be 95
|
13
|
+
end
|
14
|
+
|
15
|
+
it "creates a new subaddress with a label" do
|
16
|
+
subaddress = monero_rpc.create_address "wallet1"
|
17
|
+
expect(subaddress['address'].length).to be 95
|
18
|
+
expect(subaddress['address_index']).to be_truthy
|
19
|
+
end
|
20
|
+
|
21
|
+
it "creates an integrated address for a payment" do
|
22
|
+
address = monero_rpc.make_integrated_address
|
23
|
+
payment_id = address['payment_id']
|
24
|
+
expect(address['integrated_address'].length).to be 106
|
25
|
+
expect(payment_id.length).to be 16
|
26
|
+
end
|
27
|
+
|
28
|
+
it "lists all addresses of the wallet" do
|
29
|
+
addresses = monero_rpc.get_addresses
|
30
|
+
addresses.each do |adr|
|
31
|
+
expect(adr['address']).to be_a(String)
|
32
|
+
expect(adr['address'].length).to be 95
|
33
|
+
expect(adr['address_index']).to be_an(Integer)
|
34
|
+
expect(adr['label']).to be_a(String)
|
35
|
+
expect(adr['used']).to be(true).or(be(false))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "gets the balance of the current wallet" do
|
40
|
+
balance = monero_rpc.balance
|
41
|
+
expect(balance).to be_a(Money)
|
42
|
+
expect(balance.fractional).to be_an(Integer)
|
43
|
+
expect(balance.currency).to be_a(Money::Currency)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns a formatted balance with dot as decimal separator" do
|
47
|
+
balance = monero_rpc.balance.format
|
48
|
+
expect(balance).to be_a(String)
|
49
|
+
expect(balance).to include('.')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "gets the unlocked balance" do
|
53
|
+
balance = monero_rpc.unlocked_balance
|
54
|
+
expect(balance).to be_a(Money)
|
55
|
+
expect(balance.fractional).to be_an(Integer)
|
56
|
+
expect(balance.currency).to be_a(Money::Currency)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "gets the balance and unlocked balance" do
|
60
|
+
balance = monero_rpc.getbalance
|
61
|
+
expect(balance['balance']).to be_an(Integer)
|
62
|
+
expect(balance['unlocked_balance']).to be_an(Integer)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "gets the current block height" do
|
66
|
+
height = monero_rpc.getheight
|
67
|
+
expect(height).to be_an(Integer)
|
68
|
+
end
|
69
|
+
|
70
|
+
# the wallet locks for approx 10 blocks so this test will fail if
|
71
|
+
# balance is not yet unlocked
|
72
|
+
it "sends XMR to a standard address" do
|
73
|
+
subaddress = monero_rpc.create_address "receiving_wallet"
|
74
|
+
amount = 20075
|
75
|
+
transfer = monero_rpc.create_transfer(subaddress['address'], amount)
|
76
|
+
expect(transfer['amount']).to eq(amount)
|
77
|
+
expect(transfer['fee']).to be_an(Integer)
|
78
|
+
expect(transfer['multisig_txset']).to be_empty
|
79
|
+
expect(transfer['tx_blob']).to be_empty
|
80
|
+
expect(transfer['tx_hash']).to be_truthy
|
81
|
+
expect(transfer['tx_key']).to be_truthy
|
82
|
+
expect(transfer['tx_metadata']).to be_empty
|
83
|
+
expect(transfer['unsigned_txset']).to be_empty
|
84
|
+
end
|
85
|
+
|
86
|
+
it "sends XMR to multiple recipients" do
|
87
|
+
subaddress1 = monero_rpc.create_address "receiving_wallet"
|
88
|
+
subaddress2 = monero_rpc.create_address "receiving_wallet2"
|
89
|
+
amount1 = 20075
|
90
|
+
amount2 = 30075
|
91
|
+
|
92
|
+
recipients = [
|
93
|
+
{ address: subaddress1['address'], amount: amount1 },
|
94
|
+
{ address: subaddress2['address'], amount: amount2 }
|
95
|
+
]
|
96
|
+
|
97
|
+
transfer = monero_rpc.send_bulk_transfer(recipients)
|
98
|
+
|
99
|
+
expect(transfer['amount']).to eq(amount1+amount2)
|
100
|
+
expect(transfer['fee']).to be_an(Integer)
|
101
|
+
expect(transfer['multisig_txset']).to be_empty
|
102
|
+
expect(transfer['tx_blob']).to be_empty
|
103
|
+
expect(transfer['tx_hash']).to be_truthy
|
104
|
+
expect(transfer['tx_key']).to be_truthy
|
105
|
+
expect(transfer['tx_metadata']).to be_empty
|
106
|
+
expect(transfer['unsigned_txset']).to be_empty
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|