monero 0.0.0.8 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|