round 0.0.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/LICENSE +22 -0
- data/README.md +178 -0
- data/lib/round/account.rb +66 -0
- data/lib/round/address.rb +21 -0
- data/lib/round/application.rb +68 -0
- data/lib/round/base.rb +38 -0
- data/lib/round/client.rb +146 -0
- data/lib/round/collection.rb +51 -0
- data/lib/round/device.rb +17 -0
- data/lib/round/helpers.rb +10 -0
- data/lib/round/transaction.rb +54 -0
- data/lib/round/user.rb +56 -0
- data/lib/round/version.rb +3 -0
- data/lib/round/wallet.rb +85 -0
- data/lib/round.rb +21 -8
- data.tar.gz.sig +3 -0
- metadata +204 -9
- metadata.gz.sig +1 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e377e33cf32986ee469ccf7c60f396e05f436f9a
|
4
|
+
data.tar.gz: ffedd033550ba8633ebaf2ba67a6859006c78364
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26c9bed0c4c8dee92c7318b0a601a90c758db4679944f835bcf6b55fc5a54d91734c539533f763db64107a7145a3f8b6016a078954b064c88a5dce35a3e4cbaa
|
7
|
+
data.tar.gz: e4c83b2d6eef471e8176d4b8a710a05a539a08dfff2bdc4fa18771a6bbf8e834b76971da5abd990eebf9d6edee3bc89d64bfbe74853bd2377eaf1202842e3810
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 BitVault, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# round-rb: A Ruby client for the Gem API
|
2
|
+
The round client is designed to interact with Gem's API to make building blockchain apps drop dead simple. All the complexity of the bitcoin protocol and crypto has been abstracted away so you can focus on building your product. Here are a few of the many great things the API and clients provide:
|
3
|
+
|
4
|
+
* Multi-signature wallets with Gem as a cosigner
|
5
|
+
* Webhook notifications automatically subscribed for you
|
6
|
+
* Integrated 2FA solution with arbitrary endpoints to build into your app
|
7
|
+
* Simplified balance inqueries
|
8
|
+
* Easy address management
|
9
|
+
* Hardware Security Modules for co-signing key
|
10
|
+
* Rules engine for transactions
|
11
|
+
* SDKs for many popular languages
|
12
|
+
|
13
|
+
## Support information
|
14
|
+
* __Support email__: [support@gem.co](mailto:support@gem.co)
|
15
|
+
* __Support IRC chat__: `#gemhq` on `irc.freenode.net`
|
16
|
+
* __Issues__: Use github issues
|
17
|
+
* __Slack room__: Send email to support requesting access to the slack room for this client
|
18
|
+
* __Detailed API Docs__: http://guide.gem.co
|
19
|
+
|
20
|
+
## Installing round-rb:
|
21
|
+
### Prerequisites:
|
22
|
+
* Ruby 2.1.5
|
23
|
+
|
24
|
+
#### [Linux (debian-based, tested on Ubuntu 14.04)](docs/install.md#linux-debian-based-tested-on-ubuntu-1404)
|
25
|
+
#### [Mac OSX](docs/install.md#mac-osx)
|
26
|
+
#### [Heroku](docs/install.md#heroku)
|
27
|
+
|
28
|
+
## Getting Started Tutorial
|
29
|
+
#### Table of Contents
|
30
|
+
* [Introduction](README.md#Introduction)
|
31
|
+
* [1. Run the client](README.md#1-run-the-client)
|
32
|
+
* [2. Configure your application and API token](README.md#2-configure-your-applicaiton-and-api-token)
|
33
|
+
* [3. Create your User and Wallet](README.md#3-create-your-user-and-wallet)
|
34
|
+
* [4. Authenticate your User](README.md#4-authenticate-your-user)
|
35
|
+
* [5. Access the wallet and Default Account](README.md#5-access-the-wallet-and-default-account)
|
36
|
+
* [6. Generate an Address and Add Funds](README.md#6-generate-an-address-and-add-funds)
|
37
|
+
* [7. Make a Payment](README.md#7-make-a-payment)
|
38
|
+
* [Advanced Topics](docs/advanced.md)
|
39
|
+
* [More about Wallets and Accounts](docs/advanced.md#wallets-and-accounts)
|
40
|
+
* [More about Transactions](docs/advanced.md#transactions-and-payments)
|
41
|
+
* [Subscriptions](docs/advanced.md#subscriptions)
|
42
|
+
* [Integrated 2FA](docs/advanced.md#integrated-2fa)
|
43
|
+
* [Operational/Custodail wallet models](docs/advanced.md#operationalcustodial-wallets)
|
44
|
+
* [Operational/Custodial payments](docs/advanced.md#payments)
|
45
|
+
|
46
|
+
### Introduction
|
47
|
+
This tutorial will have you run through setting up your application and creating your own wallet as a user of your application. By the end of the tutorial, you will have created your User, wallet, account, an address as well as fund it and then make a payment using the bitcoin testnet network.
|
48
|
+
|
49
|
+
This tutorial assumes that you have completed the developer signup and that you have successfully [installed the client](docs/install.md)
|
50
|
+
|
51
|
+
### 1. Run the Client
|
52
|
+
In this step you will learn how to instantiate the API client for the given networks.
|
53
|
+
|
54
|
+
1. start your favorite interactive shell and import the round library
|
55
|
+
|
56
|
+
```bash
|
57
|
+
$ irb
|
58
|
+
> require 'round'
|
59
|
+
```
|
60
|
+
|
61
|
+
1. Create the client object using the sandbox stack
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# the default client is set to sandbox the testnet stack
|
65
|
+
client = Round.client
|
66
|
+
|
67
|
+
# if you want to configure the client for production mainnet
|
68
|
+
client = Round.client(:mainnet)
|
69
|
+
```
|
70
|
+
|
71
|
+
[[top]](README.md#getting-started-tutorial)
|
72
|
+
|
73
|
+
### 2. Configure your applicaiton and API Token
|
74
|
+
In this step your application and you will retrieve the API Token for the application and set your applications redirect url. The url is used to push the user back to your app after they complete an out of band challenge.
|
75
|
+
|
76
|
+
1. Set the redirect url by clicking in the options gear and selecting `add redirect url`
|
77
|
+
|
78
|
+
1. In the [console](https://sandbox.gem.co) copy your `api_token` by clicking on show
|
79
|
+
|
80
|
+
1. Go back to your shell session and set a variable for `api_token`
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
api_token = 'q234t09ergoasgr-9_qt4098qjergjia-asdf2490'
|
84
|
+
|
85
|
+
```
|
86
|
+
|
87
|
+
[[top]](README.md#getting-started-tutorial)
|
88
|
+
|
89
|
+
### 3. Create your User and Wallet
|
90
|
+
In this step you will create your own personal Gem user and wallet authorized on your application. This is an end-user account, which will have a 2-of-3 multisig bitcoin wallet.
|
91
|
+
|
92
|
+
1. Create your user and wallet:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# Store the device token for future authentication
|
96
|
+
device_token = client.users.create(
|
97
|
+
first_name: 'YOUR FIRST NAME',
|
98
|
+
last_name: 'YOUR LAST NAME',
|
99
|
+
email: 'YOUR EMAIL ADDRESS',
|
100
|
+
passphrase: 'aReallyStrongPassphrase',
|
101
|
+
device_name: 'SOME DEVICE NAME',
|
102
|
+
redirect_uri: 'http://something/user-device-approved')
|
103
|
+
)
|
104
|
+
```
|
105
|
+
|
106
|
+
2. Your application should **store the device_token permanently** as this will be required to authenticate from your app as this user.
|
107
|
+
3. You (acting as a user) will receive an email from Gem asking you to confirm your account and finish setup. Please follow the instructions. At the end of the User sign up flow, you'll be redirected to the redirect_uri provided in users.create (if you provided one).
|
108
|
+
|
109
|
+
[[top]](README.md#getting-started-tutorial)
|
110
|
+
|
111
|
+
### 4. Authenticate your User
|
112
|
+
In this step you will learn how to authenticate to the Gem API on a User's device to get a fully functional User object with which to perform wallet actions.
|
113
|
+
|
114
|
+
1. Call the authenticate_device method from the client object
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
full_user = client.authenticate_device(
|
118
|
+
api_token: api_token,
|
119
|
+
device_token: device_token,
|
120
|
+
email: email
|
121
|
+
)
|
122
|
+
```
|
123
|
+
|
124
|
+
[[top]](README.md#getting-started-tutorial)
|
125
|
+
|
126
|
+
### 5. Access the wallet and Default Account
|
127
|
+
[Wallets and Accounts](docs/advanced.md#wallets-and-accounts)
|
128
|
+
|
129
|
+
1. Get the wallet and then default account
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
my_account = full_user.wallet.accounts['default']
|
133
|
+
```
|
134
|
+
|
135
|
+
[[top]](README.md#getting-started-tutorial)
|
136
|
+
|
137
|
+
### 6. Generate an Address and Add Funds
|
138
|
+
In this section you'll learn how to create an address to fund with testnet coins aka funny money.
|
139
|
+
|
140
|
+
1. Create an address
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
address = my_account.addresses.create
|
144
|
+
puts address.string
|
145
|
+
puts address.path
|
146
|
+
```
|
147
|
+
1. Copy the address string and go to a faucet to fund it:
|
148
|
+
1. [TP's TestNet Faucet](https://tpfaucet.appspot.com/)
|
149
|
+
1. [Mojocoin Testnet3 Faucet](http://faucet.xeno-genesis.com/)
|
150
|
+
|
151
|
+
Payments have to be confirmed by the network and on Testnet that can be slow. To monitor for confirmations: input the address into the following url `https://live.blockcypher.com/btc-testnet/address/<YOUR ADDRESS>`. The current standard number of confirmations for a transaction to be considered safe is 6.
|
152
|
+
|
153
|
+
You will be able to make a payment on a single confirmation. While you wait for that to happen, feel free to read more details about:
|
154
|
+
[Wallets and Accounts](docs/advanced.md#wallets-and-accounts)
|
155
|
+
|
156
|
+
[[top]](README.md#getting-started-tutorial)
|
157
|
+
|
158
|
+
### 7. Make a Payment
|
159
|
+
In this section you’ll learn how to create a payment a multi-signature payment in an HD wallet. Once your address gets one more more confirmations we’ll be able to send a payment out of the wallet. To make a payment, you'll unlock a wallet, generate a list of payees and then call the pay method.
|
160
|
+
|
161
|
+
1. Unlock the wallet:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
wallet.unlock(<YOUR PASSWORD>)
|
165
|
+
```
|
166
|
+
1. Make a payment
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
transaction = account.pay([{address: 'mxzdT4ShBudVtZbMqPMh9NVM3CS56Fp11s', amount: 25000}], 1, 'http://some-redirect-uri.com/)
|
170
|
+
puts transaction.mfa_uri # redirect your user to this URI to complete payment!
|
171
|
+
```
|
172
|
+
|
173
|
+
The pay call takes a list of payee objects. A payee is a hash of `{address: ADDRESS, amount: amount}` where address is the bitcoin address and amount is the number of satoshis. `utxo_confirmations` default to 6 and represents the number of confirmations an unspent output needs to have in order to be selected for the transaction.
|
174
|
+
The last argument is the redirect uri for Gem to send the user back to your application after the user submits their MFA challenge.
|
175
|
+
|
176
|
+
**CONGRATS** - now build something cool.
|
177
|
+
|
178
|
+
[[top]](README.md#getting-started-tutorial)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Round
|
2
|
+
class Account < Round::Base
|
3
|
+
association :addresses, 'Round::AddressCollection'
|
4
|
+
association :subscriptions, 'Round::SubscriptionCollection'
|
5
|
+
|
6
|
+
attr_accessor :wallet
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
raise ArgumentError, 'Account must be associated with a wallet' unless options[:wallet]
|
10
|
+
super(options)
|
11
|
+
@wallet = options[:wallet]
|
12
|
+
end
|
13
|
+
|
14
|
+
def transactions(**query)
|
15
|
+
query[:status] = query[:status].join(',') if query[:status]
|
16
|
+
Round::TransactionCollection.new(
|
17
|
+
resource: @resource.transactions(query),
|
18
|
+
client: @client
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def pay(payees, confirmations, redirect_uri = nil, mfa_token: nil)
|
23
|
+
raise ArgumentError, 'Payees must be specified' unless payees
|
24
|
+
raise 'You must unlock the wallet before attempting a transaction' unless @wallet.multiwallet
|
25
|
+
|
26
|
+
payment = self.transactions.create(payees, confirmations, redirect_uri: redirect_uri)
|
27
|
+
signed = payment.sign(@wallet.multiwallet)
|
28
|
+
if wallet.application
|
29
|
+
mfa_token = mfa_token || @wallet.application.get_mfa
|
30
|
+
signed.approve(mfa_token)
|
31
|
+
signed.refresh
|
32
|
+
end
|
33
|
+
signed
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.hash_identifier
|
37
|
+
"name"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class AccountCollection < Round::Collection
|
43
|
+
|
44
|
+
def initialize(options = {})
|
45
|
+
raise ArgumentError, 'AccountCollection must be associated with a wallet' unless options[:wallet]
|
46
|
+
@wallet = options[:wallet]
|
47
|
+
super(options) { |account| account.wallet = @wallet }
|
48
|
+
end
|
49
|
+
|
50
|
+
def content_type
|
51
|
+
Round::Account
|
52
|
+
end
|
53
|
+
|
54
|
+
def create(name)
|
55
|
+
resource = @resource.create(name: name)
|
56
|
+
account = Round::Account.new(resource: resource, wallet: @wallet, client: @client)
|
57
|
+
add(account)
|
58
|
+
account
|
59
|
+
end
|
60
|
+
|
61
|
+
def refresh
|
62
|
+
super(wallet: @wallet)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Round
|
2
|
+
class Address < Round::Base
|
3
|
+
def self.hash_identifier
|
4
|
+
"string"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class AddressCollection < Round::Collection
|
9
|
+
|
10
|
+
def content_type
|
11
|
+
Round::Address
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
resource = @resource.create
|
16
|
+
address = Round::Address.new(resource: resource, client: @client)
|
17
|
+
self.add(address)
|
18
|
+
address
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Round
|
2
|
+
API_TOKEN = 'api_token'
|
3
|
+
TOTP_SECRET = 'totp_secret'
|
4
|
+
SUBSCRIPTION_TOKEN = 'subscription_token'
|
5
|
+
|
6
|
+
class Application < Round::Base
|
7
|
+
association :users, 'Round::UserCollection'
|
8
|
+
association :wallets, 'Round::WalletCollection'
|
9
|
+
|
10
|
+
def authorize_instance(name)
|
11
|
+
@resource.authorize_instance(name: name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def wallets
|
15
|
+
Round::WalletCollection.new(
|
16
|
+
resource: @resource.wallets,
|
17
|
+
client: @client,
|
18
|
+
application: self
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def user_from_key(key)
|
23
|
+
users.detect { |u| u.key == key }
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.hash_identifier
|
27
|
+
'name'
|
28
|
+
end
|
29
|
+
|
30
|
+
def totp=(totp_secret)
|
31
|
+
@totp = ROTP::TOTP.new(totp_secret)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_mfa
|
35
|
+
@totp.now
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_mfa!(token)
|
39
|
+
context.mfa_token = token
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset(*resets)
|
44
|
+
@resource.reset(resets)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ApplicationCollection < Round::Collection
|
50
|
+
|
51
|
+
def content_type
|
52
|
+
Round::Application
|
53
|
+
end
|
54
|
+
|
55
|
+
def create(name, callback_url = nil)
|
56
|
+
params = { name: name }
|
57
|
+
params.merge!(callback_url: callback_url) if callback_url
|
58
|
+
app_resource = @resource.create(params)
|
59
|
+
app = Round::Application.new(
|
60
|
+
resource: app_resource,
|
61
|
+
client: @client
|
62
|
+
)
|
63
|
+
add(app)
|
64
|
+
app
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
data/lib/round/base.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Round
|
2
|
+
class Base
|
3
|
+
attr_reader :resource
|
4
|
+
|
5
|
+
def initialize(resource:, client:, **kwargs)
|
6
|
+
@resource = resource
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
def refresh
|
11
|
+
@resource = @resource.get
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(meth, *args, &block)
|
16
|
+
@resource.send(meth, *args, &block)
|
17
|
+
rescue => e
|
18
|
+
@resource.attributes.fetch(meth) do
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash_identifier
|
24
|
+
send :[], self.class.hash_identifier
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.hash_identifier
|
28
|
+
"key"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.association(name, klass)
|
32
|
+
define_method(name) do
|
33
|
+
Kernel.const_get(klass).new(resource: @resource.send(name), client: @client)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/round/client.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'patchboard'
|
2
|
+
require 'base64'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Round
|
6
|
+
|
7
|
+
MAINNET_URL = 'https://api.gem.co'
|
8
|
+
SANDBOX_URL = 'https://api-sandbox.gem.co'
|
9
|
+
|
10
|
+
NETWORKS = {
|
11
|
+
testnet: :bitcoin_testnet,
|
12
|
+
bitcoin_testnet: :bitcoin_testnet,
|
13
|
+
testnet3: :bitcoin_testnet,
|
14
|
+
bitcoin: :bitcoin,
|
15
|
+
mainnet: :bitcoin,
|
16
|
+
}
|
17
|
+
|
18
|
+
def self.client(network = :bitcoin_testnet, url = nil)
|
19
|
+
network = NETWORKS.fetch(network, :bitcoin_testnet)
|
20
|
+
url ||= network == :bitcoin_testnet ? SANDBOX_URL : MAINNET_URL
|
21
|
+
|
22
|
+
@patchboard ||= ::Patchboard.discover(url) { Client::Context.new }
|
23
|
+
Client.new(@patchboard.spawn, network)
|
24
|
+
end
|
25
|
+
|
26
|
+
class Client
|
27
|
+
include Round::Helpers
|
28
|
+
|
29
|
+
attr_reader :network
|
30
|
+
|
31
|
+
def initialize(patchboard_client, network)
|
32
|
+
@patchboard_client = patchboard_client
|
33
|
+
@network = network
|
34
|
+
end
|
35
|
+
|
36
|
+
def authenticate_application(api_token:, admin_token:)
|
37
|
+
@patchboard_client
|
38
|
+
.context
|
39
|
+
.authorize(Context::Scheme::APPLICATION,
|
40
|
+
api_token: api_token, admin_token: admin_token)
|
41
|
+
authenticate_identify(api_token: api_token)
|
42
|
+
|
43
|
+
self.application.refresh
|
44
|
+
end
|
45
|
+
|
46
|
+
def context
|
47
|
+
@patchboard_client.context
|
48
|
+
end
|
49
|
+
|
50
|
+
def authenticate_identify(api_token:)
|
51
|
+
@patchboard_client
|
52
|
+
.context
|
53
|
+
.authorize(Context::Scheme::IDENTIFY, api_token: api_token)
|
54
|
+
end
|
55
|
+
|
56
|
+
def authenticate_device(email:, api_token:, device_token:)
|
57
|
+
@patchboard_client
|
58
|
+
.context
|
59
|
+
.authorize(Context::Scheme::DEVICE,
|
60
|
+
api_token: api_token, device_token: device_token)
|
61
|
+
@patchboard_client
|
62
|
+
.context
|
63
|
+
.authorize(Context::Scheme::IDENTIFY,
|
64
|
+
api_token: api_token)
|
65
|
+
self.user(email).refresh
|
66
|
+
end
|
67
|
+
|
68
|
+
def resources
|
69
|
+
@patchboard_client.resources
|
70
|
+
end
|
71
|
+
|
72
|
+
def users
|
73
|
+
UserCollection.new(resource: resources.users, client: self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def application
|
77
|
+
Application.new(resource: resources.app.get, client: self)
|
78
|
+
end
|
79
|
+
|
80
|
+
def user(email)
|
81
|
+
User.new(resource: resources.user_query(email: email), client: self)
|
82
|
+
end
|
83
|
+
|
84
|
+
class Context
|
85
|
+
module Scheme
|
86
|
+
DEVICE = 'Gem-Device'
|
87
|
+
APPLICATION = 'Gem-Application'
|
88
|
+
IDENTIFY = 'Gem-Identify'
|
89
|
+
end
|
90
|
+
|
91
|
+
SCHEMES = [Scheme::DEVICE, Scheme::APPLICATION, Scheme::IDENTIFY]
|
92
|
+
|
93
|
+
attr_accessor :schemes, :mfa_token
|
94
|
+
|
95
|
+
def initialize
|
96
|
+
@schemes = {}
|
97
|
+
end
|
98
|
+
|
99
|
+
# Is there a list of accepted params somewhere?
|
100
|
+
def authorize(scheme, params)
|
101
|
+
raise ArgumentError, 'Params cannot be empty.' if params.empty?
|
102
|
+
raise ArgumentError, 'Unknown auth scheme' unless SCHEMES.include?(scheme)
|
103
|
+
@schemes[scheme] = params
|
104
|
+
end
|
105
|
+
|
106
|
+
def compile_params(params)
|
107
|
+
compiled = params.map do |key, value|
|
108
|
+
%Q(#{key}="#{value}")
|
109
|
+
end.join(', ')
|
110
|
+
compiled << ", mfa_token=#{@mfa_token}" if @mfa_token
|
111
|
+
compiled
|
112
|
+
end
|
113
|
+
|
114
|
+
def authorizer(schemes: [], action: 'NULL ACTION', **kwargs)
|
115
|
+
schemes.each do |scheme|
|
116
|
+
if (params = @schemes[scheme])
|
117
|
+
credential = compile_params(params)
|
118
|
+
return [scheme, credential]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
raise "Action: #{action}. No authorization available for '#{schemes}'"
|
122
|
+
end
|
123
|
+
|
124
|
+
def inspect
|
125
|
+
# Hide the secret token when printed
|
126
|
+
id = "%x" % (self.object_id << 1)
|
127
|
+
%Q(#<#{self.class}:0x#{id})
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
UnknownKeyError = Class.new(StandardError)
|
132
|
+
OTPConflictError = Class.new(StandardError)
|
133
|
+
|
134
|
+
class OTPAuthFailureError < StandardError
|
135
|
+
attr_reader :key
|
136
|
+
|
137
|
+
def initialize(key)
|
138
|
+
super
|
139
|
+
@key = key
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Round
|
2
|
+
class Collection < Round::Base
|
3
|
+
include Enumerable
|
4
|
+
attr_reader :collection
|
5
|
+
|
6
|
+
def initialize(options = {}, &block)
|
7
|
+
super(options)
|
8
|
+
options.delete(:resource)
|
9
|
+
populate_data(options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def populate_data(options = {}, &block)
|
13
|
+
@collection = []
|
14
|
+
@hash = {}
|
15
|
+
@resource.list.each do |resource|
|
16
|
+
content = self.content_type.new(options.merge(resource: resource, client: @client))
|
17
|
+
yield content if block
|
18
|
+
self.add(content)
|
19
|
+
end if @resource.list
|
20
|
+
end
|
21
|
+
|
22
|
+
def refresh(options = {})
|
23
|
+
@collection = []
|
24
|
+
populate_data(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(content)
|
28
|
+
@collection << content
|
29
|
+
@hash[content.hash_identifier] = content
|
30
|
+
end
|
31
|
+
|
32
|
+
def content_type
|
33
|
+
Round::Base
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
if key.is_a?(Fixnum)
|
38
|
+
@collection[key]
|
39
|
+
else
|
40
|
+
@hash[key]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(meth, *args, &block)
|
45
|
+
@collection.send(meth, *args, &block)
|
46
|
+
rescue
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/round/device.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Round
|
2
|
+
class Device < Base
|
3
|
+
|
4
|
+
end
|
5
|
+
|
6
|
+
class DeviceCollection < Base
|
7
|
+
|
8
|
+
def create(name, redirect_uri: nil)
|
9
|
+
params = { name: name }
|
10
|
+
params[:redirect_uri] = redirect_uri if redirect_uri
|
11
|
+
auth_request_resource = @resource.create(params)
|
12
|
+
device_token = auth_request_resource.metadata.device_token
|
13
|
+
mfa_uri = auth_request_resource.mfa_uri
|
14
|
+
[device_token, mfa_uri]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Round
|
2
|
+
class Transaction < Round::Base
|
3
|
+
|
4
|
+
def sign(wallet)
|
5
|
+
raise 'transaction is already signed' unless @resource['status'] == 'unsigned'
|
6
|
+
raise 'a wallet is required to sign a transaction' unless wallet
|
7
|
+
|
8
|
+
transaction = CoinOp::Bit::Transaction.data(@resource)
|
9
|
+
raise 'bad change address' unless wallet.valid_output?(transaction.outputs.last)
|
10
|
+
|
11
|
+
@resource = @resource.update(
|
12
|
+
signatures: {
|
13
|
+
transaction_hash: transaction.hex_hash,
|
14
|
+
inputs: wallet.signatures(transaction)
|
15
|
+
}
|
16
|
+
)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def approve(mfa_token)
|
21
|
+
@client.context.mfa_token = mfa_token
|
22
|
+
@resource.approve({})
|
23
|
+
end
|
24
|
+
|
25
|
+
def transaction_hash
|
26
|
+
@resource[:hash]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.hash_identifier
|
30
|
+
'hash'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class TransactionCollection < Round::Collection
|
36
|
+
|
37
|
+
def content_type
|
38
|
+
Round::Transaction
|
39
|
+
end
|
40
|
+
|
41
|
+
def create(payees, confirmations = 6, redirect_uri: nil)
|
42
|
+
raise 'Must have list of payees' unless payees
|
43
|
+
|
44
|
+
payment_resource = @resource.create(
|
45
|
+
utxo_confirmations: confirmations,
|
46
|
+
payees: payees,
|
47
|
+
redirect_uri: redirect_uri
|
48
|
+
)
|
49
|
+
|
50
|
+
Round::Transaction.new(resource: payment_resource, client: @client)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/round/user.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Round
|
2
|
+
class User < Round::Base
|
3
|
+
association :wallets, 'Round::WalletCollection'
|
4
|
+
association :default_wallet, 'Round::Wallet'
|
5
|
+
|
6
|
+
def self.hash_identifier
|
7
|
+
'email'
|
8
|
+
end
|
9
|
+
|
10
|
+
def devices
|
11
|
+
resource = @client.resources.devices_query(
|
12
|
+
email: self.refresh.email
|
13
|
+
)
|
14
|
+
Round::DeviceCollection.new(
|
15
|
+
resource: resource,
|
16
|
+
client: @client
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def wallet
|
21
|
+
wallets.first
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class UserCollection < Round::Collection
|
26
|
+
|
27
|
+
def content_type
|
28
|
+
Round::User
|
29
|
+
end
|
30
|
+
|
31
|
+
def create(first_name:, last_name:, email:, passphrase:,
|
32
|
+
device_name:, redirect_uri: nil)
|
33
|
+
multiwallet = CoinOp::Bit::MultiWallet.generate([:primary], @client.network)
|
34
|
+
primary_seed = CoinOp::Encodings.hex(multiwallet.trees[:primary].seed)
|
35
|
+
encrypted_seed = CoinOp::Crypto::PassphraseBox.encrypt(passphrase, primary_seed)
|
36
|
+
wallet = {
|
37
|
+
name: 'default',
|
38
|
+
network: @client.network,
|
39
|
+
primary_public_seed: multiwallet.trees[:primary].to_serialized_address,
|
40
|
+
primary_private_seed: encrypted_seed
|
41
|
+
}
|
42
|
+
params = {
|
43
|
+
email: email,
|
44
|
+
first_name: first_name,
|
45
|
+
last_name: last_name,
|
46
|
+
default_wallet: wallet,
|
47
|
+
device_name: device_name,
|
48
|
+
}
|
49
|
+
params[:redirect_uri] = redirect_uri if redirect_uri
|
50
|
+
user_resource = resource.create(params)
|
51
|
+
user = Round::User.new(resource: user_resource, client: @client)
|
52
|
+
user.metadata.device_token
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/round/wallet.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
module Round
|
2
|
+
class Wallet < Round::Base
|
3
|
+
|
4
|
+
attr_reader :multiwallet, :application
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@multiwallet = options[:multiwallet]
|
8
|
+
@application = options[:application]
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def unlock(passphrase)
|
13
|
+
primary_seed = CoinOp::Crypto::PassphraseBox.decrypt(passphrase, @resource.primary_private_seed)
|
14
|
+
primary_master = MoneyTree::Master.new(seed_hex: primary_seed)
|
15
|
+
@multiwallet = CoinOp::Bit::MultiWallet.new(
|
16
|
+
private: {
|
17
|
+
primary: primary_master
|
18
|
+
},
|
19
|
+
public: {
|
20
|
+
cosigner: @resource.cosigner_public_seed,
|
21
|
+
backup: @resource.backup_public_seed
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def backup_key
|
27
|
+
@multiwallet.private_seed(:backup)
|
28
|
+
end
|
29
|
+
|
30
|
+
def accounts
|
31
|
+
Round::AccountCollection.new(
|
32
|
+
resource: @resource.accounts,
|
33
|
+
wallet: self,
|
34
|
+
client: @client
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.hash_identifier
|
39
|
+
'name'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class WalletCollection < Round::Collection
|
44
|
+
|
45
|
+
def initialize(options, &block)
|
46
|
+
super
|
47
|
+
@application = options[:application]
|
48
|
+
end
|
49
|
+
|
50
|
+
def content_type
|
51
|
+
Round::Wallet
|
52
|
+
end
|
53
|
+
|
54
|
+
def create(name, passphrase, network: 'bitcoin_testnet',
|
55
|
+
multiwallet: CoinOp::Bit::MultiWallet.generate([:primary, :backup]))
|
56
|
+
wallet_resource = create_wallet_resource(multiwallet, passphrase, name, network)
|
57
|
+
multiwallet.import(
|
58
|
+
cosigner_public_seed: wallet_resource.cosigner_public_seed
|
59
|
+
)
|
60
|
+
wallet = Round::Wallet.new(
|
61
|
+
resource: wallet_resource,
|
62
|
+
multiwallet: multiwallet,
|
63
|
+
application: @application,
|
64
|
+
client: @client
|
65
|
+
)
|
66
|
+
add(wallet)
|
67
|
+
wallet
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_wallet_resource(multiwallet, passphrase, name, network)
|
71
|
+
primary_seed = CoinOp::Encodings.hex(multiwallet.trees[:primary].seed)
|
72
|
+
## Encrypt the primary seed using a passphrase-derived key
|
73
|
+
encrypted_seed = CoinOp::Crypto::PassphraseBox.encrypt(passphrase, primary_seed)
|
74
|
+
|
75
|
+
@resource.create(
|
76
|
+
name: name,
|
77
|
+
network: network,
|
78
|
+
backup_public_seed: multiwallet.trees[:backup].to_serialized_address,
|
79
|
+
primary_public_seed: multiwallet.trees[:primary].to_serialized_address,
|
80
|
+
primary_private_seed: encrypted_seed
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
data/lib/round.rb
CHANGED
@@ -1,8 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
1
|
+
require 'rbnacl/libsodium'
|
2
|
+
require 'coin-op'
|
3
|
+
require 'rotp'
|
4
|
+
|
5
|
+
# Establish the namespace.
|
6
|
+
module Round
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'round/version'
|
11
|
+
require_relative 'round/helpers'
|
12
|
+
require_relative 'round/client'
|
13
|
+
require_relative 'round/base'
|
14
|
+
require_relative 'round/collection'
|
15
|
+
require_relative 'round/user'
|
16
|
+
require_relative 'round/application'
|
17
|
+
require_relative 'round/wallet'
|
18
|
+
require_relative 'round/device'
|
19
|
+
require_relative 'round/account'
|
20
|
+
require_relative 'round/address'
|
21
|
+
require_relative 'round/transaction'
|
data.tar.gz.sig
ADDED
metadata
CHANGED
@@ -1,23 +1,218 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: round
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Matthew King
|
8
|
+
- Julian Vergel de Dios
|
9
|
+
- James Larisch
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
|
-
cert_chain:
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
cert_chain:
|
13
|
+
- |
|
14
|
+
-----BEGIN CERTIFICATE-----
|
15
|
+
MIIDXDCCAkSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA6MQ8wDQYDVQQDDAZqdWxp
|
16
|
+
YW4xEzARBgoJkiaJk/IsZAEZFgNnZW0xEjAQBgoJkiaJk/IsZAEZFgJjbzAeFw0x
|
17
|
+
NTA0MDMwODAwMTdaFw0xNjA0MDIwODAwMTdaMDoxDzANBgNVBAMMBmp1bGlhbjET
|
18
|
+
MBEGCgmSJomT8ixkARkWA2dlbTESMBAGCgmSJomT8ixkARkWAmNvMIIBIjANBgkq
|
19
|
+
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp0qCsOUQyTRA4f7WxoU2ctpUO5+eLZw8
|
20
|
+
ILvHJy/0dC2nnIgib+FaFA8TRIGw6fjX2hQ43QJyO36zkjUhAwNu/+TBCfG+Grut
|
21
|
+
2dI9XmqU5Z6PvvXRj6Gu5IkDeIVDKILZv3bDugHJalre4BUKnwPYv5WpZ/e/c6+z
|
22
|
+
E4fwe4ZQzqslSXZo0o/wFvs5dGuIoP93bazSeqddre0JKFFiEP/SNGP9e/lXEd2V
|
23
|
+
rLFYAY409no9J+VQOHP0Nu9ShlCZp8M45abKd2ykuSDaT6jH9YcUHBr3/IEsA4+f
|
24
|
+
DypeS1ySVvad+o8iTnfz1Hyohz4ORm3spf0BOtGI/Swbv3LObZJqkwIDAQABo20w
|
25
|
+
azAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUERhBDW/kkq7tz2hN
|
26
|
+
hPNHtounnkowGAYDVR0RBBEwD4ENanVsaWFuQGdlbS5jbzAYBgNVHRIEETAPgQ1q
|
27
|
+
dWxpYW5AZ2VtLmNvMA0GCSqGSIb3DQEBBQUAA4IBAQAgnumhg8ST8JohYWcoDoQt
|
28
|
+
3BUG5rbfJ/qE0utOt6esi9d6Vz6YHpiT08woaj68OWl9U9N4vjox+ckkTRs93KBd
|
29
|
+
y3thnK9cIEAzoEZs3BBguXYOoFLughGD7hEuLlRYbwZzyIdzx/XdLgsy5Di8Gqaa
|
30
|
+
RKurfXP+dERQww34CUhmhOLO4/rYGqaD88so0MzCImgS+OX+G4ppqd38iQpaxCHL
|
31
|
+
tdc4VS7IlSRxlZ3dBOgiigy9GXpJ+7F831AqjxL39EPwdr7RguTNz+pi//RKaT/U
|
32
|
+
IlpVB+Xfk0vQdP7iYfjGxDzUf0FACMjsR95waJmadKW1Iy6STw2hwPhYIQz1Hu1A
|
33
|
+
-----END CERTIFICATE-----
|
34
|
+
date: 2015-05-04 00:00:00.000000000 Z
|
35
|
+
dependencies:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: patchboard
|
38
|
+
requirement: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0.5'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.5'
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: http
|
52
|
+
requirement: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - '='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.6.0
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.6.0
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: rotp
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - '='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 2.1.0
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - '='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.1.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rbnacl
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '3.1'
|
85
|
+
type: :runtime
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '3.1'
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: rbnacl-libsodium
|
94
|
+
requirement: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "~>"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '1.0'
|
99
|
+
type: :runtime
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - "~>"
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '1.0'
|
106
|
+
- !ruby/object:Gem::Dependency
|
107
|
+
name: coin-op
|
108
|
+
requirement: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - "~>"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0.3'
|
113
|
+
type: :runtime
|
114
|
+
prerelease: false
|
115
|
+
version_requirements: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0.3'
|
120
|
+
- !ruby/object:Gem::Dependency
|
121
|
+
name: rspec
|
122
|
+
requirement: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - "~>"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '3.0'
|
127
|
+
type: :development
|
128
|
+
prerelease: false
|
129
|
+
version_requirements: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - "~>"
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '3.0'
|
134
|
+
- !ruby/object:Gem::Dependency
|
135
|
+
name: webmock
|
136
|
+
requirement: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - "~>"
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '1.18'
|
141
|
+
type: :development
|
142
|
+
prerelease: false
|
143
|
+
version_requirements: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - "~>"
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '1.18'
|
148
|
+
- !ruby/object:Gem::Dependency
|
149
|
+
name: vcr
|
150
|
+
requirement: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - "~>"
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '2.9'
|
155
|
+
type: :development
|
156
|
+
prerelease: false
|
157
|
+
version_requirements: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - "~>"
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '2.9'
|
162
|
+
- !ruby/object:Gem::Dependency
|
163
|
+
name: pry
|
164
|
+
requirement: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - "~>"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
type: :development
|
170
|
+
prerelease: false
|
171
|
+
version_requirements: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - "~>"
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
- !ruby/object:Gem::Dependency
|
177
|
+
name: term-ansicolor
|
178
|
+
requirement: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - "~>"
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '1.3'
|
183
|
+
type: :development
|
184
|
+
prerelease: false
|
185
|
+
version_requirements: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - "~>"
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '1.3'
|
190
|
+
description: Gem is a full-stack Bitcoin API that makes it easy to build powerful
|
191
|
+
blockchain apps with beautiful, elegant code.
|
192
|
+
email:
|
193
|
+
- matthew@pandastrike.com
|
194
|
+
- julian@gem.co
|
195
|
+
- james@gem.co
|
15
196
|
executables: []
|
16
197
|
extensions: []
|
17
198
|
extra_rdoc_files: []
|
18
199
|
files:
|
200
|
+
- LICENSE
|
201
|
+
- README.md
|
19
202
|
- lib/round.rb
|
20
|
-
|
203
|
+
- lib/round/account.rb
|
204
|
+
- lib/round/address.rb
|
205
|
+
- lib/round/application.rb
|
206
|
+
- lib/round/base.rb
|
207
|
+
- lib/round/client.rb
|
208
|
+
- lib/round/collection.rb
|
209
|
+
- lib/round/device.rb
|
210
|
+
- lib/round/helpers.rb
|
211
|
+
- lib/round/transaction.rb
|
212
|
+
- lib/round/user.rb
|
213
|
+
- lib/round/version.rb
|
214
|
+
- lib/round/wallet.rb
|
215
|
+
homepage: https://github.com/GemHQ/round-rb
|
21
216
|
licenses:
|
22
217
|
- MIT
|
23
218
|
metadata: {}
|
@@ -40,5 +235,5 @@ rubyforge_project:
|
|
40
235
|
rubygems_version: 2.2.2
|
41
236
|
signing_key:
|
42
237
|
specification_version: 4
|
43
|
-
summary:
|
238
|
+
summary: Ruby client for the Gem API
|
44
239
|
test_files: []
|
metadata.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
(�P���qG#pz���uɠǒ�܌�Y�ܫ����|{3\E��"OsX���E �� @�}�
|