digitalbits-sdk 0.27.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6792d096cad91255c2b26fc1d91559e94d12ce0e934d61d561232ee2f32eb2c4
4
+ data.tar.gz: a1d4158baa655e722b14ce91b53ad13f64211fe537f2f113d35440305555803f
5
+ SHA512:
6
+ metadata.gz: b87bbaf3c2e65e4e3b06cb426316367a2547868e586adac5c35c572e53ff6210bba9d76d15da6747374b0bc036ec47726828f04135eec2589744008ea3c4b459
7
+ data.tar.gz: 2e148b6aa7cea4bf74f1bb8d9d43d04d6cb65aa1c0df3350d9689663efa4b661ab119b90317c98408978abd4cd1f53399fb9080eb114be44fea16202ae545b96
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2021 XDB Foundation
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # DigitalBIts SDK for Ruby: Frontier Integration and Higher Level Abstractions
2
+ [
3
+ This library helps you to integrate your application into the [DigitalBits network](http://digitalbits.io).
4
+
5
+ ## Installation
6
+
7
+ Add this lines to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'digitalbits-sdk'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Also requires libsodium. Installable via `brew install libsodium` on OS X.
18
+
19
+ ## Usage
20
+
21
+ See [examples](examples).
22
+
23
+ A simple payment from the root account to some random accounts
24
+
25
+ ```ruby
26
+ require 'digitalbits-sdk'
27
+
28
+ account = DigitalBits::Account.master
29
+ client = DigitalBits::Client.default_testnet()
30
+ recipient = DigitalBits::Account.random
31
+
32
+ client.send_payment({
33
+ from: account,
34
+ to: recipient,
35
+ amount: DigitalBits::Amount.new(100_000_000)
36
+ })
37
+ ```
38
+
39
+ Be sure to set the network when submitting to the public network (more information in [digitalbits-base](https://www.github.com/xdbfoundation/ruby-digitalbits-base)):
40
+
41
+ ```ruby
42
+ DigitalBits.default_network = DigitalBits::Networks::PUBLIC
43
+ ```
44
+
45
+ ## Development
46
+
47
+ - Install and activate [rvm](https://rvm.io/rvm/install)
48
+ - Ensure your `bundler` version is up-to-date: `gem install bundler`
49
+ - Run `bundle install`
50
+ - Copy `spec/config.yml.sample` to `spec/config.yml`
51
+ - Replace anything in `spec/config.yml` especially if you will re-record specs
52
+ - `bundle exec rspec spec`
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it ( https://github.com/xdbfoundation/ruby-digitalbits-sdk/fork )
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create a new Pull Request
@@ -0,0 +1,21 @@
1
+ require "digitalbits-base"
2
+
3
+ module DigitalBits
4
+ module SDK
5
+ VERSION = ::DigitalBits::VERSION
6
+ end
7
+
8
+ autoload :Account
9
+ autoload :Amount
10
+ autoload :Client
11
+ autoload :SEP10
12
+
13
+ module Frontier
14
+ extend ActiveSupport::Autoload
15
+
16
+ autoload :Problem
17
+ autoload :Result
18
+ end
19
+
20
+ autoload :TransactionPage
21
+ end
@@ -0,0 +1,89 @@
1
+ require "toml-rb"
2
+ require "uri"
3
+ require "faraday"
4
+ require "json"
5
+
6
+ module DigitalBits
7
+ class Account
8
+ delegate :address, to: :keypair
9
+
10
+ def self.random
11
+ keypair = DigitalBits::KeyPair.random
12
+ new(keypair)
13
+ end
14
+
15
+ def self.from_seed(seed)
16
+ keypair = DigitalBits::KeyPair.from_seed(seed)
17
+ new(keypair)
18
+ end
19
+
20
+ def self.from_address(address)
21
+ keypair = DigitalBits::KeyPair.from_address(address)
22
+ new(keypair)
23
+ end
24
+
25
+ def self.lookup(federated_name)
26
+ _, domain = federated_name.split("*")
27
+ if domain.nil?
28
+ raise InvalidFederationAddress.new
29
+ end
30
+
31
+ domain_req = Faraday.new("https://#{domain}/.well-known/digitalbits.toml").get
32
+
33
+ unless domain_req.status == 200
34
+ raise InvalidDigitalBitsDomain.new("Domain does not contain digitalbits.toml file")
35
+ end
36
+
37
+ fed_server_url = TomlRB.parse(domain_req.body)["FEDERATION_SERVER"]
38
+ if fed_server_url.nil?
39
+ raise InvalidDigitalBitsTOML.new("Invalid DigitalBits TOML file")
40
+ end
41
+
42
+ unless fed_server_url&.match?(URI::DEFAULT_PARSER.make_regexp)
43
+ raise InvalidFederationURL.new("Invalid Federation Server URL")
44
+ end
45
+
46
+ lookup_req = Faraday.new(fed_server_url).get { |req|
47
+ req.params[:q] = federated_name
48
+ req.params[:type] = "name"
49
+ }
50
+
51
+ unless lookup_req.status == 200
52
+ raise AccountNotFound.new("Account not found")
53
+ end
54
+
55
+ JSON.parse(lookup_req.body)["account_id"]
56
+ end
57
+
58
+ def self.master
59
+ keypair = DigitalBits::KeyPair.from_raw_seed("allmylifemyhearthasbeensearching")
60
+ new(keypair)
61
+ end
62
+
63
+ attr_reader :keypair
64
+
65
+ # @param [DigitalBits::KeyPair] keypair
66
+ def initialize(keypair)
67
+ @keypair = keypair
68
+ end
69
+
70
+ def to_keypair
71
+ keypair
72
+ end
73
+ end
74
+
75
+ class AccountNotFound < StandardError
76
+ end
77
+
78
+ class InvalidDigitalBitsTOML < StandardError
79
+ end
80
+
81
+ class InvalidFederationAddress < StandardError
82
+ end
83
+
84
+ class InvalidDigitalBitsDomain < StandardError
85
+ end
86
+
87
+ class InvalidFederationURL < StandardError
88
+ end
89
+ end
@@ -0,0 +1,36 @@
1
+ module DigitalBits
2
+ class Amount
3
+ attr_reader :amount
4
+ attr_reader :asset
5
+
6
+ # @param [Fixnum] amount
7
+ # @param [DigitalBits::Asset] asset
8
+ def initialize(amount, asset = DigitalBits::Asset.native)
9
+ # TODO: how are we going to handle decimal considerations?
10
+
11
+ @amount = amount
12
+ @asset = asset
13
+ end
14
+
15
+ # @return [Array(Symbol, Fixnum)] in case of a native asset
16
+ # @return [Array(Symbol, String, DigitalBits::KeyPair, Fixnum)] in case of alphanum asset
17
+ def to_payment
18
+ case asset.type
19
+ when AssetType.asset_type_native
20
+ [:native, amount]
21
+ when AssetType.asset_type_credit_alphanum4
22
+ keypair = KeyPair.from_public_key(asset.issuer.value)
23
+ [:alphanum4, asset.code, keypair, amount]
24
+ when AssetType.asset_type_credit_alphanum12
25
+ keypair = KeyPair.from_public_key(asset.issuer.value)
26
+ [:alphanum12, asset.code, keypair, amount]
27
+ else
28
+ raise "Unknown asset type: #{asset.type}"
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ "#<DigitalBits::Amount #{asset}(#{amount})>"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,298 @@
1
+ require "hyperclient"
2
+ require "active_support/core_ext/object/blank"
3
+ require "securerandom"
4
+
5
+ module DigitalBits
6
+ class AccountRequiresMemoError < StandardError
7
+ attr_reader :account_id, :operation_index
8
+
9
+ def initialize(message, account_id, operation_index)
10
+ super(message)
11
+ @account_id = account_id
12
+ @operation_index = operation_index
13
+ end
14
+ end
15
+
16
+ class Client
17
+ DEFAULT_FEE = 100
18
+
19
+ FRONTIER_LOCALHOST_URL = "http://127.0.0.1:8000"
20
+ FRONTIER_MAINNET_URL = "https://frontier.livenet.digitalbits.io"
21
+ FRONTIER_TESTNET_URL = "https://frontier.testnet.digitalbits.io"
22
+ FRIENDBOT_URL = "https://friendbot.testnet.digitalbits.io".freeze
23
+
24
+ def self.default(options = {})
25
+ new options.merge(
26
+ frontier: FRONTIER_MAINNET_URL
27
+ )
28
+ end
29
+
30
+ def self.default_testnet(options = {})
31
+ new options.merge(
32
+ frontier: FRONTIER_TESTNET_URL,
33
+ friendbot: FRONTIER_TESTNET_URL
34
+ )
35
+ end
36
+
37
+ def self.localhost(options = {})
38
+ new options.merge(
39
+ frontier: FRONTIER_LOCALHOST_URL
40
+ )
41
+ end
42
+
43
+ attr_reader :frontier
44
+
45
+ # @option options [String] :frontier The Frontier server URL.
46
+ def initialize(options)
47
+ @options = options
48
+ @frontier = Hyperclient.new(options[:frontier]) { |client|
49
+ client.faraday_block = lambda do |conn|
50
+ conn.use Faraday::Response::RaiseError
51
+ conn.use FaradayMiddleware::FollowRedirects
52
+ conn.request :url_encoded
53
+ conn.response :hal_json, content_type: /\bjson$/
54
+ conn.adapter :excon
55
+ end
56
+ client.headers = {
57
+ "Accept" => "application/hal+json,application/problem+json,application/json",
58
+ "X-Client-Name" => "ruby-digitalbits-sdk",
59
+ "X-Client-Version" => VERSION
60
+ }
61
+ }
62
+ end
63
+
64
+ # @param [DigitalBits::Account|String] account_or_address
65
+ def account_info(account_or_address)
66
+ account_id = if account_or_address.is_a?(DigitalBits::Account)
67
+ account_or_address.address
68
+ else
69
+ account_or_address
70
+ end
71
+ @frontier.account(account_id: account_id)._get
72
+ end
73
+
74
+ # @option options [DigitalBits::Account] :account
75
+ # @option options [DigitalBits::Account] :destination
76
+ def account_merge(options = {})
77
+ account = options[:account]
78
+ destination = options[:destination]
79
+ sequence = options[:sequence] || (account_info(account).sequence.to_i + 1)
80
+
81
+ transaction = DigitalBits::TransactionBuilder.account_merge(
82
+ source_account: destination.keypair,
83
+ sequence_number: sequence,
84
+ destination: destination.keypair
85
+ )
86
+
87
+ envelope = transaction.to_envelope(account.keypair)
88
+ submit_transaction(tx_envelope: envelope)
89
+ end
90
+
91
+ def friendbot(account)
92
+ uri = URI.parse(FRIENDBOT_URL)
93
+ uri.query = "addr=#{account.address}"
94
+ Faraday.post(uri.to_s)
95
+ end
96
+
97
+ # @option options [DigitalBits::Account] :account
98
+ # @option options [DigitalBits::Account] :funder
99
+ # @option options [Integer] :starting_balance
100
+ def create_account(options = {})
101
+ funder = options[:funder]
102
+ sequence = options[:sequence] || (account_info(funder).sequence.to_i + 1)
103
+ # In the future, the fee should be grabbed from the network's last transactions,
104
+ # instead of using a hard-coded default value.
105
+ fee = options[:fee] || DEFAULT_FEE
106
+
107
+ payment = DigitalBits::TransactionBuilder.create_account(
108
+ source_account: funder.keypair,
109
+ sequence_number: sequence,
110
+ base_fee: fee,
111
+ destination: options[:account].keypair,
112
+ starting_balance: options[:starting_balance]
113
+ )
114
+ envelope = payment.to_envelope(funder.keypair)
115
+ submit_transaction(tx_envelope: envelope)
116
+ end
117
+
118
+ # @option options [DigitalBits::Account] :from The source account
119
+ # @option options [DigitalBits::Account] :to The destination account
120
+ # @option options [DigitalBits::Amount] :amount The amount to send
121
+ def send_payment(options = {})
122
+ from_account = options[:from]
123
+ tx_source_account = options[:transaction_source] || from_account
124
+ op_source_account = from_account if tx_source_account.present?
125
+
126
+ sequence = options[:sequence] ||
127
+ (account_info(tx_source_account).sequence.to_i + 1)
128
+
129
+ payment = DigitalBits::TransactionBuilder.new(
130
+ source_account: tx_source_account.keypair,
131
+ sequence_number: sequence
132
+ ).add_operation(
133
+ DigitalBits::Operation.payment(
134
+ source_account: op_source_account.keypair,
135
+ destination: options[:to].keypair,
136
+ amount: options[:amount].to_payment
137
+ )
138
+ ).set_memo(options[:memo]).set_timeout(0).build
139
+
140
+ signers = [tx_source_account, op_source_account].uniq(&:address)
141
+ to_envelope_args = signers.map(&:keypair)
142
+
143
+ envelope = payment.to_envelope(*to_envelope_args)
144
+ submit_transaction(tx_envelope: envelope)
145
+ end
146
+
147
+ # @option options [DigitalBits::Account] :account
148
+ # @option options [Integer] :limit
149
+ # @option options [Integer] :cursor
150
+ # @return [DigitalBits::TransactionPage]
151
+ def transactions(options = {})
152
+ args = options.slice(:limit, :cursor)
153
+
154
+ resource = if options[:account]
155
+ args = args.merge(account_id: options[:account].address)
156
+ @frontier.account_transactions(args)
157
+ else
158
+ @frontier.transactions(args)
159
+ end
160
+
161
+ TransactionPage.new(resource)
162
+ end
163
+
164
+ # @param [Array(Symbol,String,DigitalBits::KeyPair|DigitalBits::Account)] asset
165
+ # @param [DigitalBits::Account] source
166
+ # @param [Integer] sequence
167
+ # @param [Integer] fee
168
+ # @param [Integer] limit
169
+ def change_trust(
170
+ asset:,
171
+ source:,
172
+ sequence: nil,
173
+ fee: DEFAULT_FEE,
174
+ limit: nil
175
+ )
176
+ sequence ||= (account_info(source).sequence.to_i + 1)
177
+
178
+ op_args = {
179
+ account: source.keypair,
180
+ sequence: sequence,
181
+ line: asset
182
+ }
183
+ op_args[:limit] = limit unless limit.nil?
184
+
185
+ tx = DigitalBits::TransactionBuilder.change_trust(
186
+ source_account: source.keypair,
187
+ sequence_number: sequence,
188
+ **op_args
189
+ )
190
+
191
+ envelope = tx.to_envelope(source.keypair)
192
+ submit_transaction(tx_envelope: envelope)
193
+ end
194
+
195
+ # @param [DigitalBits::TransactionEnvelope] tx_envelope
196
+ # @option options [Boolean] :skip_memo_required_check (false)
197
+ def submit_transaction(tx_envelope:, options: {skip_memo_required_check: false})
198
+ unless options[:skip_memo_required_check]
199
+ check_memo_required(tx_envelope)
200
+ end
201
+ @frontier.transactions._post(tx: tx_envelope.to_xdr(:base64))
202
+ end
203
+
204
+ # Required by SEP-0029
205
+ # @param [DigitalBits::TransactionEnvelope] tx_envelope
206
+ def check_memo_required(tx_envelope)
207
+ tx = tx_envelope.tx
208
+
209
+ if tx.is_a?(DigitalBits::FeeBumpTransaction)
210
+ tx = tx.inner_tx.v1!.tx
211
+ end
212
+
213
+ # Check transactions where the .memo field is nil or of type MemoType.memo_none
214
+ if !tx.memo.nil? && tx.memo.type != DigitalBits::MemoType.memo_none
215
+ return
216
+ end
217
+
218
+ destinations = Set.new
219
+ ot = DigitalBits::OperationType
220
+
221
+ tx.operations.each_with_index do |op, idx|
222
+ destination = case op.body.type
223
+ when ot.payment, ot.path_payment_strict_receive, ot.path_payment_strict_send
224
+ op.body.value.destination
225
+ when ot.account_merge
226
+ # There is no AccountMergeOp, op.body is an Operation object
227
+ # and op.body.value is a PublicKey (or AccountID) object.
228
+ op.body.value
229
+ else
230
+ next
231
+ end
232
+
233
+ if destinations.include?(destination) || destination.switch == DigitalBits::CryptoKeyType.key_type_muxed_ed25519
234
+ next
235
+ end
236
+
237
+ destinations.add(destination)
238
+ kp = DigitalBits::KeyPair.from_public_key(destination.value)
239
+
240
+ begin
241
+ info = account_info(kp.address)
242
+ rescue Faraday::ResourceNotFound
243
+ # Don't raise an error if its a 404, but throw one otherwise
244
+ next
245
+ end
246
+ if info.data["config.memo_required"] == "MQ=="
247
+ # MQ== is the base64 encoded string for the string "1"
248
+ raise AccountRequiresMemoError.new("account requires memo", destination, idx)
249
+ end
250
+ end
251
+ end
252
+
253
+ # DEPRECATED: this function has been moved DigitalBits::SEP10.build_challenge_tx and
254
+ # will be removed in the next major version release.
255
+ #
256
+ # A wrapper function for DigitalBits::SEP10::build_challenge_tx.
257
+ #
258
+ # @param server [DigitalBits::KeyPair] Keypair for server's signing account.
259
+ # @param client [DigitalBits::KeyPair] Keypair for the account whishing to authenticate with the server.
260
+ # @param anchor_name [String] Anchor's name to be used in the manage_data key.
261
+ # @param timeout [Integer] Challenge duration (default to 5 minutes).
262
+ #
263
+ # @return [String] A base64 encoded string of the raw TransactionEnvelope xdr struct for the transaction.
264
+ def build_challenge_tx(server:, client:, anchor_name:, timeout: 300)
265
+ DigitalBits::SEP10.build_challenge_tx(
266
+ server: server, client: client, anchor_name: anchor_name, timeout: timeout
267
+ )
268
+ end
269
+
270
+ # DEPRECATED: this function has been moved to DigitalBits::SEP10::read_challenge_tx and
271
+ # will be removed in the next major version release.
272
+ #
273
+ # A wrapper function for DigitalBits::SEP10.verify_challenge_transaction
274
+ #
275
+ # @param challenge [String] SEP0010 transaction challenge in base64.
276
+ # @param server [DigitalBits::KeyPair] DigitalBits::KeyPair for server where the challenge was generated.
277
+ #
278
+ # @return [Boolean]
279
+ def verify_challenge_tx(challenge:, server:)
280
+ DigitalBits::SEP10.verify_challenge_tx(challenge_xdr: challenge, server: server)
281
+ true
282
+ end
283
+
284
+ # DEPRECATED: this function has been moved to DigitalBits::SEP10::verify_tx_signed_by and
285
+ # will be removed in the next major version release.
286
+ #
287
+ # @param transaction_envelope [DigitalBits::TransactionEnvelope]
288
+ # @param keypair [DigitalBits::KeyPair]
289
+ #
290
+ # @return [Boolean]
291
+ #
292
+ def verify_tx_signed_by(transaction_envelope:, keypair:)
293
+ DigitalBits::SEP10.verify_tx_signed_by(
294
+ tx_envelope: transaction_envelope, keypair: keypair
295
+ )
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,45 @@
1
+ module DigitalBits
2
+ module Frontier
3
+ class Problem
4
+ def initialize(attributes)
5
+ @attributes = attributes.reverse_merge({
6
+ type: "about:blank",
7
+ title: "Unknown Error",
8
+ status: 500
9
+ })
10
+
11
+ @meta = @attributes.except!(:type, :title, :status, :detail, :instance)
12
+ end
13
+
14
+ # @return [String]
15
+ def type
16
+ @attributes[:type]
17
+ end
18
+
19
+ # @return [String]
20
+ def title
21
+ @attributes[:title]
22
+ end
23
+
24
+ # @return [Integer]
25
+ def status
26
+ @attributes[:status]
27
+ end
28
+
29
+ # @return [String]
30
+ def detail
31
+ @attributes[:detail]
32
+ end
33
+
34
+ # @return [String]
35
+ def instance
36
+ @attributes[:instance]
37
+ end
38
+
39
+ # @return [{String => Object}]
40
+ def meta
41
+ @attributes[:instance]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,274 @@
1
+ module DigitalBits
2
+ class InvalidSep10ChallengeError < StandardError; end
3
+
4
+ class SEP10
5
+ include DigitalBits::DSL
6
+
7
+ # Helper method to create a valid challenge transaction which you can use for DigitalBits Web Authentication.
8
+ #
9
+ # @example
10
+ # server = DigitalBits::KeyPair.random # SIGNING_KEY from your digitalbits.toml
11
+ # user = DigitalBits::KeyPair.from_address('G...')
12
+ # DigitalBits::SEP10.build_challenge_tx(server: server, client: user, domain: 'example.com', timeout: 300)
13
+ #
14
+ # @param server [DigitalBits::KeyPair] server's signing keypair (SIGNING_KEY in service's digitalbits.toml)
15
+ # @param client [DigitalBits::KeyPair] account trying to authenticate with the server
16
+ # @param domain [String] service's domain to be used in the manage_data key
17
+ # @param timeout [Integer] challenge duration (default to 5 minutes)
18
+ #
19
+ # @return [String] A base64 encoded string of the raw TransactionEnvelope xdr struct for the transaction.
20
+ #
21
+ def self.build_challenge_tx(server:, client:, domain: nil, timeout: 300, **options)
22
+ if domain.blank? && options.key?(:anchor_name)
23
+ ActiveSupport::Deprecation.new("next release", "digitalbits-sdk").warn <<~MSG
24
+ SEP-10 v2.0.0 requires usage of service home domain instead of anchor name in the challenge transaction.
25
+ Please update your implementation to use `DigitalBits::SEP10.build_challenge_tx(..., home_domain: 'example.com')`.
26
+ Using `anchor_name` parameter makes your service incompatible with SEP10-2.0 clients, support for this parameter
27
+ is deprecated and will be removed in the next major release of digitalbits-base.
28
+ MSG
29
+ domain = options[:anchor_name]
30
+ end
31
+
32
+ now = Time.now.to_i
33
+ time_bounds = DigitalBits::TimeBounds.new(
34
+ min_time: now,
35
+ max_time: now + timeout
36
+ )
37
+
38
+ tb = DigitalBits::TransactionBuilder.new(
39
+ source_account: server,
40
+ sequence_number: 0,
41
+ time_bounds: time_bounds
42
+ )
43
+
44
+ # The value must be 64 bytes long. It contains a 48 byte
45
+ # cryptographic-quality random string encoded using base64 (for a total of
46
+ # 64 bytes after encoding).
47
+ tb.add_operation(
48
+ DigitalBits::Operation.manage_data(
49
+ name: "#{domain} auth",
50
+ value: SecureRandom.base64(48),
51
+ source_account: client
52
+ )
53
+ )
54
+
55
+ if options.key?(:auth_domain)
56
+ tb.add_operation(
57
+ DigitalBits::Operation.manage_data(
58
+ name: "web_auth_domain",
59
+ value: options[:auth_domain],
60
+ source_account: server
61
+ )
62
+ )
63
+ end
64
+
65
+ tb.build.to_envelope(server).to_xdr(:base64)
66
+ end
67
+
68
+ # Reads a SEP 10 challenge transaction and returns the decoded transaction envelope and client account ID contained within.
69
+ #
70
+ # It also verifies that transaction is signed by the server.
71
+ #
72
+ # It does not verify that the transaction has been signed by the client or
73
+ # that any signatures other than the servers on the transaction are valid.
74
+ # Use either {.verify_challenge_tx_threshold} or {.verify_challenge_tx_signers} to completely verify
75
+ # the signed challenge
76
+ #
77
+ # @example
78
+ # sep10 = DigitalBits::SEP10
79
+ # server = DigitalBits::KeyPair.random # this should be the SIGNING_KEY from your digitalbits.toml
80
+ # challenge = sep10.build_challenge_tx(server: server, client: user, domain: domain, timeout: timeout)
81
+ # envelope, client_address = sep10.read_challenge_tx(server: server, challenge_xdr: challenge)
82
+ #
83
+ # @param challenge_xdr [String] SEP0010 transaction challenge in base64.
84
+ # @param server [DigitalBits::KeyPair] keypair for server where the challenge was generated.
85
+ #
86
+ # @return [Array(DigitalBits::TransactionEnvelope, String)]
87
+ def self.read_challenge_tx(server:, challenge_xdr:, **options)
88
+ envelope = DigitalBits::TransactionEnvelope.from_xdr(challenge_xdr, "base64")
89
+ transaction = envelope.tx
90
+
91
+ if transaction.seq_num != 0
92
+ raise InvalidSep10ChallengeError, "The transaction sequence number should be zero"
93
+ end
94
+
95
+ if transaction.source_account != server.muxed_account
96
+ raise InvalidSep10ChallengeError, "The transaction source account is not equal to the server's account"
97
+ end
98
+
99
+ if transaction.operations.size < 1
100
+ raise InvalidSep10ChallengeError, "The transaction should contain at least one operation"
101
+ end
102
+
103
+ auth_op, *rest_ops = transaction.operations
104
+ client_account_id = auth_op.source_account
105
+
106
+ auth_op_body = auth_op.body.value
107
+
108
+ if client_account_id.blank?
109
+ raise InvalidSep10ChallengeError, "The transaction's operation should contain a source account"
110
+ end
111
+
112
+ if auth_op.body.arm != :manage_data_op
113
+ raise InvalidSep10ChallengeError, "The transaction's first operation should be manageData"
114
+ end
115
+
116
+ if options.key?(:domain) && auth_op_body.data_name != "#{options[:domain]} auth"
117
+ raise InvalidSep10ChallengeError, "The transaction's operation data name is invalid"
118
+ end
119
+
120
+ if auth_op_body.data_value.unpack1("m").size != 48
121
+ raise InvalidSep10ChallengeError, "The transaction's operation value should be a 64 bytes base64 random string"
122
+ end
123
+
124
+ rest_ops.each do |op|
125
+ body = op.body
126
+
127
+ if body.arm != :manage_data_op
128
+ raise InvalidSep10ChallengeError, "The transaction has operations that are not of type 'manageData'"
129
+ elsif op.source_account != server.muxed_account
130
+ raise InvalidSep10ChallengeError, "The transaction has operations that are unrecognized"
131
+ else
132
+ op_params = body.value
133
+ if op_params.data_name == "web_auth_domain" && options.key?(:auth_domain) && op_params.data_value != options[:auth_domain]
134
+ raise InvalidSep10ChallengeError, "The transaction has 'manageData' operation with 'web_auth_domain' key and invalid value"
135
+ end
136
+ end
137
+ end
138
+
139
+ unless verify_tx_signed_by(tx_envelope: envelope, keypair: server)
140
+ raise InvalidSep10ChallengeError, "The transaction is not signed by the server"
141
+ end
142
+
143
+ time_bounds = transaction.time_bounds
144
+ now = Time.now.to_i
145
+
146
+ if time_bounds.blank? || !now.between?(time_bounds.min_time, time_bounds.max_time)
147
+ raise InvalidSep10ChallengeError, "The transaction has expired"
148
+ end
149
+
150
+ # Mirror the return type of the other SDK's and return a string
151
+ client_kp = DigitalBits::KeyPair.from_public_key(client_account_id.ed25519!)
152
+
153
+ [envelope, client_kp.address]
154
+ end
155
+
156
+ # Verifies that for a SEP 10 challenge transaction all signatures on the transaction
157
+ # are accounted for and that the signatures meet a threshold on an account. A
158
+ # transaction is verified if it is signed by the server account, and all other
159
+ # signatures match a signer that has been provided as an argument, and those
160
+ # signatures meet a threshold on the account.
161
+ #
162
+ # @param server [DigitalBits::KeyPair] keypair for server's account.
163
+ # @param challenge_xdr [String] SEP0010 challenge transaction in base64.
164
+ # @param signers [{String => Integer}] The signers of client account.
165
+ # @param threshold [Integer] The medThreshold on the client account.
166
+ #
167
+ # @raise InvalidSep10ChallengeError if the transaction has unrecognized signatures (only server's
168
+ # signing key and keypairs found in the `signing` argument are recognized) or total weight of
169
+ # the signers does not meet the `threshold`
170
+ #
171
+ # @return [<String>] subset of input signers who have signed `challenge_xdr`
172
+ def self.verify_challenge_tx_threshold(server:, challenge_xdr:, signers:, threshold:)
173
+ signers_found = verify_challenge_tx_signers(
174
+ server: server, challenge_xdr: challenge_xdr, signers: signers.keys
175
+ )
176
+
177
+ total_weight = signers.values_at(*signers_found).sum
178
+
179
+ if total_weight < threshold
180
+ raise InvalidSep10ChallengeError, "signers with weight #{total_weight} do not meet threshold #{threshold}."
181
+ end
182
+
183
+ signers_found
184
+ end
185
+
186
+ # Verifies that for a SEP 10 challenge transaction all signatures on the transaction are accounted for.
187
+ #
188
+ # A transaction is verified if it is signed by the server account, and all other signatures match a signer
189
+ # that has been provided as an argument. Additional signers can be provided that do not have a signature,
190
+ # but all signatures must be matched to a signer for verification to succeed.
191
+ #
192
+ # If verification succeeds a list of signers that were found is returned, excluding the server account ID.
193
+ #
194
+ # @param server [DigitalBits::Keypair] server's signing key
195
+ # @param challenge_xdr [String] SEP0010 transaction challenge transaction in base64.
196
+ # @param signers [<String>] The signers of client account.
197
+ #
198
+ # @raise InvalidSep10ChallengeError one or more signatures in the transaction are not identifiable
199
+ # as the server account or one of the signers provided in the arguments
200
+ #
201
+ # @return [<String>] subset of input signers who have signed `challenge_xdr`
202
+ def self.verify_challenge_tx_signers(server:, challenge_xdr:, signers:)
203
+ raise InvalidSep10ChallengeError, "no signers provided" if signers.empty?
204
+
205
+ te, _ = read_challenge_tx(server: server, challenge_xdr: challenge_xdr)
206
+
207
+ # ignore non-G signers and server's own address
208
+ client_signers = signers.select { |s| s =~ /G[A-Z0-9]{55}/ && s != server.address }.to_set
209
+ raise InvalidSep10ChallengeError, "at least one regular signer must be provided" if client_signers.empty?
210
+
211
+ # verify all signatures in one pass
212
+ client_signers.add(server.address)
213
+ signers_found = verify_tx_signatures(tx_envelope: te, signers: client_signers)
214
+
215
+ # ensure server signed transaction and remove it
216
+ unless signers_found.delete?(server.address)
217
+ raise InvalidSep10ChallengeError, "Transaction not signed by server: #{server.address}"
218
+ end
219
+
220
+ # Confirm we matched signatures to the client signers.
221
+ if signers_found.empty?
222
+ raise InvalidSep10ChallengeError, "Transaction not signed by any client signer."
223
+ end
224
+
225
+ # Confirm all signatures were consumed by a signer.
226
+ if signers_found.size != te.signatures.length - 1
227
+ raise InvalidSep10ChallengeError, "Transaction has unrecognized signatures."
228
+ end
229
+
230
+ signers_found
231
+ end
232
+
233
+ # Verifies every signer passed matches a signature on the transaction exactly once,
234
+ # returning a list of unique signers that were found to have signed the transaction.
235
+ #
236
+ # @param tx_envelope [DigitalBits::TransactionEnvelope] SEP0010 transaction challenge transaction envelope.
237
+ # @param signers [<String>] The signers of client account.
238
+ #
239
+ # @return [Set<DigitalBits::KeyPair>]
240
+ def self.verify_tx_signatures(tx_envelope:, signers:)
241
+ signatures = tx_envelope.signatures
242
+ if signatures.empty?
243
+ raise InvalidSep10ChallengeError, "Transaction has no signatures."
244
+ end
245
+
246
+ tx_hash = tx_envelope.tx.hash
247
+ to_keypair = DigitalBits::DSL.method(:KeyPair)
248
+ keys_by_hint = signers.map(&to_keypair).index_by(&:signature_hint)
249
+
250
+ tx_envelope.signatures.each.with_object(Set.new) do |sig, result|
251
+ key = keys_by_hint.delete(sig.hint)
252
+ result.add(key.address) if key&.verify(sig.signature, tx_hash)
253
+ end
254
+ end
255
+
256
+ # Verifies if a DigitalBits::TransactionEnvelope was signed by the given DigitalBits::KeyPair
257
+ #
258
+ # @example
259
+ # DigitalBits::SEP10.verify_tx_signed_by(tx_envelope: envelope, keypair: keypair)
260
+ #
261
+ # @param tx_envelope [DigitalBits::TransactionEnvelope]
262
+ # @param keypair [DigitalBits::KeyPair]
263
+ #
264
+ # @return [Boolean]
265
+ def self.verify_tx_signed_by(tx_envelope:, keypair:)
266
+ tx_hash = tx_envelope.tx.hash
267
+ tx_envelope.signatures.any? do |sig|
268
+ next if sig.hint != keypair.signature_hint
269
+
270
+ keypair.verify(sig.signature, tx_hash)
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,25 @@
1
+ module DigitalBits
2
+ class TransactionPage
3
+ include Enumerable
4
+
5
+ # @param [Hyperclient::Link] resource
6
+ def initialize(resource)
7
+ @resource = resource
8
+ end
9
+
10
+ def each
11
+ @resource.records.each do |tx|
12
+ yield tx if block_given?
13
+ end
14
+ end
15
+
16
+ # @return [DigitalBits::TransactionPage]
17
+ def next_page
18
+ self.class.new(@resource.next)
19
+ end
20
+
21
+ def next_page!
22
+ @resource = @resource.next
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: digitalbits-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.27.1
5
+ platform: ruby
6
+ authors:
7
+ - XDB Foundation
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-08-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: digitalbits-base
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.27.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.27.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.0
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '7.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 5.0.0
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '7.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: excon
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 0.71.0
54
+ - - "<"
55
+ - !ruby/object:Gem::Version
56
+ version: '1.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.71.0
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: hyperclient
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 0.7.0
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 0.7.0
84
+ - - "<"
85
+ - !ruby/object:Gem::Version
86
+ version: '2.0'
87
+ - !ruby/object:Gem::Dependency
88
+ name: toml-rb
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 1.1.1
94
+ - - "<"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ type: :runtime
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 1.1.1
104
+ - - "<"
105
+ - !ruby/object:Gem::Version
106
+ version: '3.0'
107
+ - !ruby/object:Gem::Dependency
108
+ name: bundler
109
+ requirement: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '2.2'
114
+ type: :development
115
+ prerelease: false
116
+ version_requirements: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - "~>"
119
+ - !ruby/object:Gem::Version
120
+ version: '2.2'
121
+ - !ruby/object:Gem::Dependency
122
+ name: rake
123
+ requirement: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - "~>"
126
+ - !ruby/object:Gem::Version
127
+ version: '13'
128
+ type: :development
129
+ prerelease: false
130
+ version_requirements: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - "~>"
133
+ - !ruby/object:Gem::Version
134
+ version: '13'
135
+ - !ruby/object:Gem::Dependency
136
+ name: rspec
137
+ requirement: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - "~>"
140
+ - !ruby/object:Gem::Version
141
+ version: '3.9'
142
+ type: :development
143
+ prerelease: false
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - "~>"
147
+ - !ruby/object:Gem::Version
148
+ version: '3.9'
149
+ description:
150
+ email:
151
+ executables: []
152
+ extensions: []
153
+ extra_rdoc_files:
154
+ - README.md
155
+ - LICENSE
156
+ files:
157
+ - LICENSE
158
+ - README.md
159
+ - lib/digitalbits-sdk.rb
160
+ - lib/digitalbits/account.rb
161
+ - lib/digitalbits/amount.rb
162
+ - lib/digitalbits/client.rb
163
+ - lib/digitalbits/frontier/problem.rb
164
+ - lib/digitalbits/sep10.rb
165
+ - lib/digitalbits/transaction_page.rb
166
+ homepage: https://github.com/xdbfoundation/ruby-digitalbits-sdk
167
+ licenses:
168
+ - Apache-2.0
169
+ metadata:
170
+ bug_tracker_uri: https://github.com/xdbfoundation/ruby-digitalbits-sdk/issues
171
+ changelog_uri: https://github.com/xdbfoundation/ruby-digitalbits-sdk/blob/v0.27.1/sdk/CHANGELOG.md
172
+ documentation_uri: https://rubydoc.info/gems/digitalbits-sdk/0.27.1/
173
+ github_repo: ssh://github.com/xdbfoundation/ruby-digitalbits-sdk
174
+ homepage_uri: https://github.com/xdbfoundation/ruby-digitalbits-sdk/tree/main/sdk
175
+ source_code_uri: https://github.com/xdbfoundation/ruby-digitalbits-sdk/tree/v0.27.1/sdk
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: 2.5.0
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.1.6
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: DigitalBits client library
195
+ test_files: []