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.
@@ -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
@@ -1,6 +1,4 @@
1
- module RPC
2
-
1
+ module MoneroRPC
3
2
  class Payment
4
3
  end
5
-
6
4
  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 RPC
26
- # => {"integrated_address"=>"A7TmpAyaPeZLnugTKRSwGJhW4vnYv8RAVdRvYyvbistbHUnojyTHyHcYpbZvbTZHDsi4rF1EK5TiYgnCN6FWM9HjfwGRvbCHYCZAaKSzDx", "payment_id"=>"c7e7146b3335aa54"}
27
-
28
- class Transfer
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
- mixin = args.fetch(:mixin, 4)
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
- RPC::Client.request("transfer", options)
52
- end
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,3 @@
1
+ module MoneroRPC
2
+ VERSION = "0.0.12"
3
+ 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
@@ -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 'rpc/version'
4
+ require 'monero_rpc/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "monero"
8
- spec.version = RPC::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 'sqlite3'
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
24
 
25
25
  spec.required_rubygems_version = '>= 1.3.6'
26
26
 
@@ -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