pochette_toshi 0.1.0
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/README.md +48 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/pochette_toshi.rb +226 -0
- data/lib/pochette_toshi/version.rb +3 -0
- data/pochette_toshi.gemspec +26 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5f50e277f409de70f366b93f9677902028df8b17
|
4
|
+
data.tar.gz: 1f536b620dc7930970051e56f14b5339b22a9b64
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b1b9ddb4759fa69eb26a61bde1d1c549f0b302aae9e230204bf7c99384538b3983cb417d62dd1541b8c15bea1a123629ae25d6b89a4e6cb0d83a773044c027f0
|
7
|
+
data.tar.gz: 92dc48c252c261316593f2536627b5434c5a2b5a809c8f0556a1fecf994d1b51637a9442506962c4a4f79582ac371989b97dcb2cb6abbe2ab52955191375e988
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# PochetteToshi
|
2
|
+
|
3
|
+
A [Pochette](https://github.com/bitex-la/pochette) backend using Toshi.
|
4
|
+
|
5
|
+
It will connect to your Toshi postgres database directly (not using Toshi's JSON RPC)
|
6
|
+
|
7
|
+
For better performance it is recommended you create the following indexes in your
|
8
|
+
database:
|
9
|
+
|
10
|
+
CREATE INDEX inputs_hsh_index on inputs (hsh);
|
11
|
+
CREATE INDEX inputs_is_coinbase_index on inputs (prev_out, hsh) WHERE prev_out = '0000000000000000000000000000000000000000000000000000000000000000';
|
12
|
+
CREATE INDEX unspent_outputs_usable_index on unspent_outputs (amount) WHERE amount > 5000;
|
13
|
+
CREATE INDEX transactions_hsh_height ON transactions (hsh, height);
|
14
|
+
|
15
|
+
You can instatiate a Toshi backend passing your postgres connection options, they will be passed
|
16
|
+
to the pg gem [as seen in their docs](http://deveiate.org/code/pg/PG/Connection.html#method-c-new)
|
17
|
+
|
18
|
+
>>> Pochette::Backends::Toshi.new(host: 'your-db-host', dbname: 'toshi')
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'pochette_toshi'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install pochette_toshi
|
35
|
+
|
36
|
+
## Development
|
37
|
+
|
38
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
39
|
+
|
40
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
41
|
+
|
42
|
+
## Contributing
|
43
|
+
|
44
|
+
1. Fork it ( https://github.com/[my-github-username]/pochette_toshi/fork )
|
45
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
46
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
47
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
48
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "pochette_toshi"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
require "pochette_toshi/version"
|
2
|
+
require "active_support"
|
3
|
+
require "active_support/core_ext"
|
4
|
+
require "pg"
|
5
|
+
|
6
|
+
module Pochette
|
7
|
+
module Backends
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# This class is not properly tested.
|
12
|
+
# Be careful when changing anything, and/or make sure
|
13
|
+
# you can run and assert your changes with a local copy of
|
14
|
+
# a toshi testnet database.
|
15
|
+
class Pochette::Backends::Toshi
|
16
|
+
attr_accessor :connection
|
17
|
+
|
18
|
+
def initialize(options)
|
19
|
+
self.connection = PG.connect(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def incoming_for(addresses, min_date)
|
23
|
+
addresses.in_groups_of(500, false).collect do |group|
|
24
|
+
incoming_for_helper(group, min_date)
|
25
|
+
end.flatten(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def incoming_for_helper(addresses, min_date)
|
29
|
+
addresses_sql = sanitize_list(addresses)
|
30
|
+
current_height = block_height
|
31
|
+
from_block = current_height - ((Time.now - min_date) / 60 / 60 * 6).ceil
|
32
|
+
|
33
|
+
query(%{
|
34
|
+
SELECT ale.amount, a.address, t.hsh,
|
35
|
+
(#{current_height + 1} - t.height) as confirmations, o.position,
|
36
|
+
(SELECT string_agg(a2.address,',') as sender
|
37
|
+
FROM address_ledger_entries ale2
|
38
|
+
INNER JOIN addresses a2 ON a2.id = ale2.address_id
|
39
|
+
WHERE ale2.transaction_id = t.id AND ale2.input_id IS NOT NULL
|
40
|
+
) as senders
|
41
|
+
FROM address_ledger_entries ale
|
42
|
+
INNER JOIN addresses a ON a.id = ale.address_id AND a.address in (#{addresses_sql})
|
43
|
+
INNER JOIN transactions t ON t.id = ale.transaction_id AND t.pool = 1 AND t.height > #{from_block}
|
44
|
+
INNER JOIN outputs o ON o.id = ale.output_id AND o.branch = 0
|
45
|
+
UNION
|
46
|
+
SELECT ale.amount, a.address, t.hsh, 0 as confirmations, o.position,
|
47
|
+
(
|
48
|
+
SELECT string_agg(a2.address,',') as sender
|
49
|
+
FROM unconfirmed_ledger_entries ale2
|
50
|
+
INNER JOIN unconfirmed_addresses a2 ON a2.id = ale2.address_id
|
51
|
+
WHERE ale2.transaction_id = t.id AND ale2.input_id IS NOT NULL
|
52
|
+
) as senders
|
53
|
+
FROM unconfirmed_ledger_entries ale
|
54
|
+
INNER JOIN unconfirmed_addresses a ON a.id = ale.address_id AND a.address in (#{addresses_sql})
|
55
|
+
INNER JOIN unconfirmed_transactions t ON t.id = ale.transaction_id AND t.pool = 1
|
56
|
+
INNER JOIN unconfirmed_outputs o ON o.id = ale.output_id
|
57
|
+
}).collect{|a,addr,hsh,confs,pos,sender| [a.to_i, addr, hsh, confs.to_i, pos.to_i, sender] }
|
58
|
+
end
|
59
|
+
|
60
|
+
def balances_for(addresses, confirmations)
|
61
|
+
addresses.in_groups_of(500, false).reduce({}) do |accum, group|
|
62
|
+
accum.merge!(balances_for_helper(group, confirmations))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def balances_for_helper(addresses, confirmations)
|
67
|
+
# Addresses have denormalized sent and received columns for all
|
68
|
+
# transactions. We must then calculate which portion
|
69
|
+
# of that is unconfirmed in order to get the 'confirmed' balances.
|
70
|
+
# And to get the 'unconfirmed' balances we also need to fetch
|
71
|
+
# ledger entries for transactions that were not added to a block yet.
|
72
|
+
addresses_sql = sanitize_list(addresses)
|
73
|
+
|
74
|
+
confirmed_id_to_address = {}
|
75
|
+
unconfirmed_id_to_address = {}
|
76
|
+
result = addresses.reduce({}) do |accum, address|
|
77
|
+
accum[address] = [0,0,0,0,0,0]
|
78
|
+
accum
|
79
|
+
end
|
80
|
+
|
81
|
+
query(%{
|
82
|
+
SELECT id, address, total_received, total_sent
|
83
|
+
FROM addresses WHERE address in (#{addresses_sql})
|
84
|
+
}).each do |id, address, received, sent|
|
85
|
+
confirmed_id_to_address[id] = address
|
86
|
+
received = received.to_d / 1_0000_0000
|
87
|
+
sent = sent.to_d / 1_0000_0000
|
88
|
+
balance = received - sent
|
89
|
+
result[address] = [received, sent, balance, received, sent, balance]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Now we take the previous 1 confirmation balances and substract
|
93
|
+
# what was below threshold to get the 'confirmed' balances.
|
94
|
+
query(%{
|
95
|
+
SELECT ale.address_id,
|
96
|
+
sum(CASE WHEN ale.amount > 0 THEN ale.amount ELSE 0 END) as received,
|
97
|
+
sum(CASE WHEN ale.amount < 0 THEN ale.amount ELSE 0 END) as sent
|
98
|
+
FROM address_ledger_entries ale
|
99
|
+
INNER JOIN addresses a ON a.id = ale.address_id AND a.address in (#{addresses_sql})
|
100
|
+
INNER JOIN transactions t ON t.id = ale.transaction_id AND t.pool = 1
|
101
|
+
AND t.height > #{block_height - confirmations + 1}
|
102
|
+
GROUP BY ale.address_id
|
103
|
+
}).each do |id, received, sent|
|
104
|
+
row = result[confirmed_id_to_address[id]]
|
105
|
+
row[0] = row[0] - (received.to_d / 1_0000_0000)
|
106
|
+
row[1] = row[1] - (sent.to_d.abs / 1_0000_0000)
|
107
|
+
row[2] = row[0] - row[1]
|
108
|
+
end
|
109
|
+
|
110
|
+
query(%{SELECT id, address
|
111
|
+
FROM unconfirmed_addresses WHERE address in (#{addresses_sql})
|
112
|
+
}).each do |id, address|
|
113
|
+
unconfirmed_id_to_address[id] = address
|
114
|
+
end
|
115
|
+
|
116
|
+
# And then we also add the unconfirmed stuff to the already
|
117
|
+
# cached 1 confirmation balances
|
118
|
+
query(%{
|
119
|
+
SELECT ale.address_id,
|
120
|
+
sum(CASE WHEN ale.amount > 0 THEN ale.amount ELSE 0 END) as received,
|
121
|
+
sum(CASE WHEN ale.amount < 0 THEN ale.amount ELSE 0 END) as sent
|
122
|
+
FROM unconfirmed_ledger_entries ale
|
123
|
+
INNER JOIN unconfirmed_addresses a ON a.id = ale.address_id
|
124
|
+
AND a.address in (#{addresses_sql})
|
125
|
+
INNER JOIN unconfirmed_transactions t ON t.id = ale.transaction_id AND t.pool = 1
|
126
|
+
GROUP BY ale.address_id
|
127
|
+
}).each do |id, received, sent|
|
128
|
+
row = result[unconfirmed_id_to_address[id]]
|
129
|
+
row[3] += (received.to_d / 1_0000_0000)
|
130
|
+
row[4] += (sent.to_d.abs / 1_0000_0000)
|
131
|
+
row[5] = row[3] - row[4]
|
132
|
+
end
|
133
|
+
|
134
|
+
return result
|
135
|
+
end
|
136
|
+
|
137
|
+
# Performs the low level queries to the toshi database
|
138
|
+
# and returns a JSON structure for the unspents and the transactions.
|
139
|
+
# THIS METHOD IS NOT TESTED IN CI!!!! DO NOT JUST REFACTOR THIS WITHOUT
|
140
|
+
# TESTING IT LOCALLY AGAINST THE ~20 GB TOSHI TESTNET DATABASE.
|
141
|
+
def list_unspent(addresses)
|
142
|
+
unspents = []
|
143
|
+
addresses.in_groups_of(500, false).collect do |group|
|
144
|
+
unspents += list_unspent_helper(group)
|
145
|
+
end
|
146
|
+
unspents
|
147
|
+
end
|
148
|
+
|
149
|
+
def list_unspent_helper(addresses)
|
150
|
+
addresses_sql = sanitize_list(addresses)
|
151
|
+
query(%{
|
152
|
+
SELECT a.address, o.hsh, o.position, uo.amount
|
153
|
+
FROM addresses a
|
154
|
+
INNER JOIN unspent_outputs uo ON uo.address_id = a.id AND uo.amount > 5000
|
155
|
+
LEFT JOIN outputs o ON o.id = uo.output_id
|
156
|
+
WHERE a.address in (#{addresses_sql})
|
157
|
+
AND NOT EXISTS (
|
158
|
+
SELECT i.id FROM inputs i
|
159
|
+
LEFT JOIN transactions t ON t.hsh = i.hsh
|
160
|
+
WHERE i.hsh = o.hsh AND
|
161
|
+
i.prev_out = '0000000000000000000000000000000000000000000000000000000000000000'
|
162
|
+
AND t.height > #{block_height - 100}
|
163
|
+
)
|
164
|
+
}).collect{|a,b,c,d| [a,b,c.to_i,d.to_i]}
|
165
|
+
end
|
166
|
+
|
167
|
+
def list_transactions(txids)
|
168
|
+
transactions = []
|
169
|
+
addresses.in_groups_of(500, false).collect do |group|
|
170
|
+
transactions += list_transactions_helper(group)
|
171
|
+
end
|
172
|
+
transactions
|
173
|
+
end
|
174
|
+
|
175
|
+
def list_transactions_helper(hashes)
|
176
|
+
transactions_sql = sanitize_list(hashes)
|
177
|
+
transactions = query(%{SELECT * FROM transactions WHERE hsh IN (#{transactions_sql})})
|
178
|
+
inputs = query(%{SELECT * FROM inputs WHERE hsh IN (#{transactions_sql})
|
179
|
+
ORDER BY position})
|
180
|
+
inputs_by_hsh = inputs.reduce({}) do |d, i|
|
181
|
+
d[i[1]] ||= []
|
182
|
+
d[i[1]] << i
|
183
|
+
d
|
184
|
+
end
|
185
|
+
outputs = query(%{SELECT * FROM outputs WHERE hsh IN (#{transactions_sql})
|
186
|
+
ORDER BY position})
|
187
|
+
outputs_by_hsh = outputs.reduce({}) do |d, o|
|
188
|
+
d[o[1]] ||= []
|
189
|
+
d[o[1]] << o
|
190
|
+
d
|
191
|
+
end
|
192
|
+
|
193
|
+
transactions_json = transactions.collect do |_, hsh, ver, lock_time|
|
194
|
+
inputs_json = inputs_by_hsh[hsh].collect do |_, _, prev, index, script, seq, pos|
|
195
|
+
{ prev_hash: prev,
|
196
|
+
prev_index: index.to_i,
|
197
|
+
sequence: [seq[2..-1]].pack('H*').unpack('V')[0],
|
198
|
+
script_sig: script[2..-1]
|
199
|
+
}
|
200
|
+
end
|
201
|
+
outputs_json = outputs_by_hsh[hsh].collect do |_, _, amount, script|
|
202
|
+
{ amount: amount.to_i, script_pubkey: script[2..-1] }
|
203
|
+
end
|
204
|
+
{ hash: hsh,
|
205
|
+
version: ver.to_i,
|
206
|
+
lock_time: lock_time.to_i,
|
207
|
+
inputs: inputs_json,
|
208
|
+
bin_outputs: outputs_json
|
209
|
+
}
|
210
|
+
end
|
211
|
+
transactions_json
|
212
|
+
end
|
213
|
+
|
214
|
+
def block_height
|
215
|
+
connection.exec('select max(height) from blocks where branch = 0').values[0][0].to_i
|
216
|
+
end
|
217
|
+
|
218
|
+
def query(sql)
|
219
|
+
connection.exec(sql).values
|
220
|
+
end
|
221
|
+
|
222
|
+
def sanitize_list(list)
|
223
|
+
list.collect{|a| "'#{a}'"}.join(',')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pochette_toshi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pochette_toshi"
|
8
|
+
spec.version = PochetteToshi::VERSION
|
9
|
+
spec.authors = ["Nubis"]
|
10
|
+
spec.email = ["yo@nubis.im"]
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.summary = "Toshi backend for pochette"
|
14
|
+
spec.description = "Toshi backend for the pochette wallet library"
|
15
|
+
spec.homepage = "https://github.com/bitex-la/pochette-toshi"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
spec.add_dependency "pochette", "~> 0.1"
|
20
|
+
spec.add_dependency "pg", "~> 0.17"
|
21
|
+
spec.add_dependency "activesupport", "~> 4.2"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pochette_toshi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nubis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pochette
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.17'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.17'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.9'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2'
|
97
|
+
description: Toshi backend for the pochette wallet library
|
98
|
+
email:
|
99
|
+
- yo@nubis.im
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".travis.yml"
|
107
|
+
- Gemfile
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- bin/console
|
111
|
+
- bin/setup
|
112
|
+
- lib/pochette_toshi.rb
|
113
|
+
- lib/pochette_toshi/version.rb
|
114
|
+
- pochette_toshi.gemspec
|
115
|
+
homepage: https://github.com/bitex-la/pochette-toshi
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.4.5
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Toshi backend for pochette
|
139
|
+
test_files: []
|