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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dd856ad0da3b256d27fc46333562be670c50619e
4
- data.tar.gz: 946f4ad0661ccba1e2fe45c32625b1544b20190d
3
+ metadata.gz: e377e33cf32986ee469ccf7c60f396e05f436f9a
4
+ data.tar.gz: ffedd033550ba8633ebaf2ba67a6859006c78364
5
5
  SHA512:
6
- metadata.gz: d54ea2fb15da19481702b18342e68d28c7c515fbc07e22813c46bb633895fbd5dcdf1456888932ecb01c03624107332db446a78672bd98e384ca5dbfe0796019
7
- data.tar.gz: 2ad7ea5aae5d3dc675d3d2138ad5261aac2ee9af0815cd0926d6830cd0b0d5910a27cd4d2f4bb10de37a357a18b4144bd8bed25d15db1c75ae3a4e8ec7b1277f
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
@@ -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
@@ -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,10 @@
1
+ module Round
2
+ module Helpers
3
+ def extract_params(header)
4
+ header.scan(/(\S*)\=\"(\S*)\"/).inject({}) {|memo, match|
5
+ memo[match[0].to_sym] = match[1]
6
+ memo
7
+ }
8
+ end
9
+ end
10
+ 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
@@ -0,0 +1,3 @@
1
+ module Round
2
+ VERSION = "0.7.0"
3
+ end
@@ -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
- class Round
2
- def self.shape
3
- puts "Round!"
4
- end
5
- def self.cut
6
- puts "flawless"
7
- end
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
@@ -0,0 +1,3 @@
1
+ ���D�"D<{(̇�D�����B��?��T~��{����Io�f���� -@>��M� děyfރ�1��'�kާ�F��[Ǜ��X67����Ӎ_��C�X�A ��&��m�۲8.��;6�Cj�(��RBe� ��7��f`���Se)��
2
+ Dܵ�������x���w�U�9�@=���Q��z�f_8J��K���S���
3
+ ;��#e�_�j�~+/�ӆ��7�M��BYh���Y�dgt� ��^�1���
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.0.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
- - JMAN
7
+ - Matthew King
8
+ - Julian Vergel de Dios
9
+ - James Larisch
8
10
  autorequire:
9
11
  bindir: bin
10
- cert_chain: []
11
- date: 2014-08-29 00:00:00.000000000 Z
12
- dependencies: []
13
- description: A simple gem for the type of cut
14
- email: ''
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
- homepage: http://rubygems.org/gems/round
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: The cut!
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 �� @�}�