tronzap-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +21 -0
  4. data/README.es.md +396 -0
  5. data/README.md +392 -0
  6. data/README.pt-br.md +392 -0
  7. data/README.ru.md +392 -0
  8. data/lib/tronzap/client.rb +239 -0
  9. data/lib/tronzap/coerce.rb +127 -0
  10. data/lib/tronzap/configuration.rb +140 -0
  11. data/lib/tronzap/errors.rb +150 -0
  12. data/lib/tronzap/http_adapter.rb +168 -0
  13. data/lib/tronzap/models/activate_address_rate.rb +11 -0
  14. data/lib/tronzap/models/address_resources.rb +13 -0
  15. data/lib/tronzap/models/aml_risk_factor.rb +28 -0
  16. data/lib/tronzap/models/bandwidth_rate.rb +28 -0
  17. data/lib/tronzap/models/direct_recharge_rate.rb +38 -0
  18. data/lib/tronzap/models/energy_rate.rb +44 -0
  19. data/lib/tronzap/models/enums.rb +26 -0
  20. data/lib/tronzap/models/resource_amounts.rb +13 -0
  21. data/lib/tronzap/models/timestamp.rb +58 -0
  22. data/lib/tronzap/models/transaction_params.rb +46 -0
  23. data/lib/tronzap/requests/address_activation.rb +28 -0
  24. data/lib/tronzap/requests/aml_check.rb +68 -0
  25. data/lib/tronzap/requests/aml_history.rb +34 -0
  26. data/lib/tronzap/requests/bandwidth_transaction.rb +33 -0
  27. data/lib/tronzap/requests/calculate.rb +32 -0
  28. data/lib/tronzap/requests/check_transaction.rb +48 -0
  29. data/lib/tronzap/requests/energy_transaction.rb +42 -0
  30. data/lib/tronzap/requests/estimate_energy.rb +38 -0
  31. data/lib/tronzap/requests/resource_bundle_transaction.rb +51 -0
  32. data/lib/tronzap/requests/validation.rb +56 -0
  33. data/lib/tronzap/response_decoder.rb +115 -0
  34. data/lib/tronzap/responses/account_balance.rb +20 -0
  35. data/lib/tronzap/responses/address_info.rb +26 -0
  36. data/lib/tronzap/responses/aml_check.rb +56 -0
  37. data/lib/tronzap/responses/aml_history.rb +28 -0
  38. data/lib/tronzap/responses/aml_service.rb +30 -0
  39. data/lib/tronzap/responses/calculation.rb +40 -0
  40. data/lib/tronzap/responses/direct_recharge_info.rb +22 -0
  41. data/lib/tronzap/responses/energy_estimate.rb +44 -0
  42. data/lib/tronzap/responses/service_rates.rb +36 -0
  43. data/lib/tronzap/responses/transaction.rb +44 -0
  44. data/lib/tronzap/version.rb +6 -0
  45. data/lib/tronzap-sdk.rb +4 -0
  46. data/lib/tronzap.rb +47 -0
  47. metadata +107 -0
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tronzap
4
+ # Reads loosely typed JSON values into the types the models expose.
5
+ #
6
+ # @api private
7
+ module Coerce
8
+ # A value in the response does not have the expected shape.
9
+ class Error < StandardError; end
10
+
11
+ EMPTY_HASH = {}.freeze
12
+ EMPTY_ARRAY = [].freeze
13
+
14
+ module_function
15
+
16
+ # The API is backed by PHP, which encodes an empty associative array as [].
17
+ def object(value, name)
18
+ return EMPTY_HASH if value.nil? || value == EMPTY_ARRAY
19
+ raise Error, "#{name} is not an object" unless value.is_a?(Hash)
20
+
21
+ value
22
+ end
23
+
24
+ def list(value, name, &)
25
+ return EMPTY_ARRAY if value.nil? || value == EMPTY_HASH
26
+ raise Error, "#{name} is not an array" unless value.is_a?(Array)
27
+
28
+ value.map(&).freeze
29
+ end
30
+
31
+ def text(value, name)
32
+ case value
33
+ when nil then ""
34
+ when String then value
35
+ when Integer, true, false then value.to_s
36
+ when BigDecimal then value.to_s("F")
37
+ else raise Error, "#{name} is not a string"
38
+ end
39
+ end
40
+
41
+ def optional_text(value, name)
42
+ result = text(value, name)
43
+ result.empty? ? nil : result
44
+ end
45
+
46
+ def integer(value, name)
47
+ return 0 if value.nil? || (value.is_a?(String) && value.strip.empty?)
48
+ return value if value.is_a?(Integer)
49
+
50
+ number = to_decimal(value, name)
51
+ raise Error, "#{name} is not an integer: #{value.inspect}" unless number.frac.zero?
52
+
53
+ number.to_i
54
+ end
55
+
56
+ def decimal(value, name)
57
+ optional_decimal(value, name) || BigDecimal(0)
58
+ end
59
+
60
+ def optional_decimal(value, name)
61
+ return nil if value.nil? || (value.is_a?(String) && value.strip.empty?)
62
+
63
+ to_decimal(value, name)
64
+ end
65
+
66
+ # Amounts arrive as JSON numbers from some endpoints and as strings from others.
67
+ def to_decimal(value, name)
68
+ case value
69
+ when BigDecimal then value
70
+ when Integer then BigDecimal(value)
71
+ when Float then BigDecimal(value.to_s)
72
+ when String then parse_decimal(value, name)
73
+ else raise Error, "#{name} is not a number: #{value.inspect}"
74
+ end
75
+ end
76
+
77
+ def parse_decimal(value, name)
78
+ number = BigDecimal(value.strip)
79
+ raise Error, "#{name} is not a finite number: #{value.inspect}" unless number.finite?
80
+
81
+ number
82
+ rescue ArgumentError
83
+ raise Error, "#{name} is not a number: #{value.inspect}"
84
+ end
85
+
86
+ def boolean(value, name)
87
+ case value
88
+ when nil, false then false
89
+ when true then true
90
+ when Integer, BigDecimal then !value.zero?
91
+ when String then parse_boolean(value, name)
92
+ else raise Error, "#{name} is not a boolean: #{value.inspect}"
93
+ end
94
+ end
95
+
96
+ def parse_boolean(value, name)
97
+ case value.strip
98
+ when "true", "1" then true
99
+ when "", "false", "0" then false
100
+ else raise Error, "#{name} is not a boolean: #{value.inspect}"
101
+ end
102
+ end
103
+
104
+ def decimal_map(value, name)
105
+ object(value, name).to_h { |key, amount| [key.to_s.freeze, to_decimal(amount, "#{name}.#{key}")] }.freeze
106
+ end
107
+
108
+ def timestamp(value, name)
109
+ case value
110
+ when nil then nil
111
+ when String then value.strip.empty? ? nil : Models::Timestamp.parse(value)
112
+ when Integer then Models::Timestamp.parse(value.to_s)
113
+ else raise Error, "#{name} is not a timestamp: #{value.inspect}"
114
+ end
115
+ end
116
+
117
+ # Values the API may add later are reported as +:unknown+ instead of failing the response.
118
+ def enum(value, allowed, name)
119
+ wire = text(value, name)
120
+ allowed.find { |symbol| symbol.name == wire } || :unknown
121
+ end
122
+
123
+ def optional_enum(value, allowed, name)
124
+ optional_text(value, name) && enum(value, allowed, name)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Tronzap
6
+ # Settings of a {Client}. Every client owns its configuration: the client validates and freezes it when it is
7
+ # created, so there is no shared or global state.
8
+ #
9
+ # @example In the constructor
10
+ # Tronzap::Client.new(api_token: token, api_secret: secret, timeout: 10)
11
+ #
12
+ # @example In a block
13
+ # Tronzap::Client.new do |config|
14
+ # config.api_token = token
15
+ # config.api_secret = secret
16
+ # config.base_url = "api.tronzap.com"
17
+ # end
18
+ class Configuration
19
+ # The production API endpoint.
20
+ DEFAULT_BASE_URL = "https://api.tronzap.com"
21
+
22
+ # The default timeout, in seconds.
23
+ DEFAULT_TIMEOUT = 30
24
+
25
+ # @return [String] the API token from your TronZap dashboard, sent as a bearer token. Required.
26
+ attr_accessor :api_token
27
+
28
+ # @return [String] the API secret from your TronZap dashboard, used to sign every request body. It is never
29
+ # sent. Required.
30
+ attr_accessor :api_secret
31
+
32
+ # @return [String] the API endpoint. A bare host such as +api.tronzap.com+ gets the +https+ scheme and a
33
+ # trailing slash is removed. Give an explicit scheme to opt out, for example +http://localhost:8080+.
34
+ attr_accessor :base_url
35
+
36
+ # @return [Numeric] seconds allowed for connecting and for each read or write of a request
37
+ attr_accessor :timeout
38
+
39
+ # @return [String] the +User-Agent+ header
40
+ attr_accessor :user_agent
41
+
42
+ # @return [#call] the HTTP adapter, see {HttpAdapter}
43
+ attr_accessor :adapter
44
+
45
+ # @param api_token [String, nil]
46
+ # @param api_secret [String, nil]
47
+ # @param base_url [String]
48
+ # @param timeout [Numeric]
49
+ # @param user_agent [String]
50
+ # @param adapter [#call, nil] defaults to {HttpAdapter::NetHttp}
51
+ def initialize(api_token: nil, api_secret: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT,
52
+ user_agent: "tronzap-sdk-ruby/#{VERSION}", adapter: nil)
53
+ @api_token = api_token
54
+ @api_secret = api_secret
55
+ @base_url = base_url
56
+ @timeout = timeout
57
+ @user_agent = user_agent
58
+ @adapter = adapter
59
+ end
60
+
61
+ # Validates and normalizes the settings, then freezes the configuration.
62
+ #
63
+ # @return [self]
64
+ # @raise [ArgumentError] when a setting is invalid
65
+ def finalize!
66
+ @api_token = credential(@api_token, "api_token")
67
+ @api_secret = credential(@api_secret, "api_secret")
68
+ @base_url = self.class.normalize_base_url(@base_url)
69
+ @timeout = validate_timeout(@timeout)
70
+ @user_agent = validate_user_agent(@user_agent)
71
+ @adapter = validate_adapter(@adapter || HttpAdapter::NetHttp.new)
72
+ freeze
73
+ end
74
+
75
+ # @return [String] a description that leaves out the credentials
76
+ def inspect
77
+ "#<#{self.class.name} base_url=#{@base_url.inspect} timeout=#{@timeout.inspect} " \
78
+ "user_agent=#{@user_agent.inspect} adapter=#{@adapter.class.name}>"
79
+ end
80
+
81
+ # @param base_url [String]
82
+ # @return [String] the URL with a scheme and without a trailing slash
83
+ # @raise [ArgumentError] when it is not an http or https URL
84
+ def self.normalize_base_url(base_url)
85
+ raise ArgumentError, "base_url must be a String" unless base_url.is_a?(String)
86
+
87
+ normalized = base_url.strip
88
+ raise ArgumentError, "base_url must not be blank" if normalized.empty?
89
+
90
+ normalized = "https://#{normalized.delete_prefix("/")}" unless normalized.include?("://")
91
+ normalized = normalized.sub(%r{/+\z}, "")
92
+ check_url(normalized, base_url)
93
+ normalized
94
+ end
95
+
96
+ def self.check_url(url, original)
97
+ uri = begin
98
+ URI.parse(url)
99
+ rescue URI::InvalidURIError
100
+ raise ArgumentError, "base_url is not a valid URL: #{original}"
101
+ end
102
+ raise ArgumentError, "base_url must use http or https: #{original}" unless %w[http https].include?(uri.scheme)
103
+ raise ArgumentError, "base_url has no host: #{original}" if uri.host.nil? || uri.host.empty?
104
+ return unless uri.query || uri.fragment
105
+
106
+ raise ArgumentError, "base_url must not have a query or fragment: #{original}"
107
+ end
108
+ private_class_method :check_url
109
+
110
+ private
111
+
112
+ def credential(value, name)
113
+ raise ArgumentError, "#{name} is required" unless value.is_a?(String) && !value.strip.empty?
114
+
115
+ value.dup.freeze
116
+ end
117
+
118
+ def validate_timeout(value)
119
+ unless value.is_a?(Numeric) && value.real? && value.positive? && value.to_f.finite?
120
+ raise ArgumentError, "timeout must be a positive number of seconds, got #{value.inspect}"
121
+ end
122
+
123
+ value
124
+ end
125
+
126
+ def validate_user_agent(value)
127
+ unless value.is_a?(String) && !value.strip.empty? && !value.match?(/[\r\n]/)
128
+ raise ArgumentError, "user_agent must be a non-blank single-line String"
129
+ end
130
+
131
+ value.dup.freeze
132
+ end
133
+
134
+ def validate_adapter(value)
135
+ raise ArgumentError, "adapter must respond to #call" unless value.respond_to?(:call)
136
+
137
+ value
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tronzap
4
+ # The base class of every failure of a TronZap API call.
5
+ #
6
+ # Invalid arguments are not API failures: they raise +ArgumentError+ before anything is sent.
7
+ class Error < StandardError; end
8
+
9
+ # The API rejected the request with a non-zero error code.
10
+ #
11
+ # The API reports some errors with a 2xx status and others with 4xx or 5xx, so this error takes precedence over
12
+ # {HttpError} whenever the response carries an error code.
13
+ class ApiError < Error
14
+ # @return [Integer] the numeric error code, one of the {ErrorCode} constants or a code this SDK does not know
15
+ attr_reader :code
16
+
17
+ # @return [String, nil] the error key, for example +invalid_tron_address+ or
18
+ # +invalid_tron_address.from_address+
19
+ attr_reader :error_key
20
+
21
+ # @return [String, nil] the ID the API assigned to the request. Quote it when contacting support.
22
+ attr_reader :request_id
23
+
24
+ # @return [Integer] the HTTP status of the response
25
+ attr_reader :status
26
+
27
+ # @return [String] the raw response body
28
+ attr_reader :response_body
29
+
30
+ # @param message [String] the error message from the API
31
+ # @param code [Integer] the numeric error code
32
+ # @param status [Integer] the HTTP status of the response
33
+ # @param response_body [String] the raw response body
34
+ # @param error_key [String, nil] the error key
35
+ # @param request_id [String, nil] the request ID
36
+ def initialize(message, code:, status:, response_body:, error_key: nil, request_id: nil)
37
+ super(message)
38
+ @code = code
39
+ @status = status
40
+ @response_body = response_body
41
+ @error_key = error_key
42
+ @request_id = request_id
43
+ end
44
+ end
45
+
46
+ # The API answered with a non-2xx status and no error code in the body.
47
+ class HttpError < Error
48
+ # @return [Integer] the HTTP status of the response
49
+ attr_reader :status
50
+
51
+ # @return [String] the raw response body
52
+ attr_reader :response_body
53
+
54
+ # @param message [String] the error message
55
+ # @param status [Integer] the HTTP status of the response
56
+ # @param response_body [String] the raw response body
57
+ def initialize(message, status:, response_body:)
58
+ super(message)
59
+ @status = status
60
+ @response_body = response_body
61
+ end
62
+ end
63
+
64
+ # The API answered with HTTP 429: too many requests. Back off and retry.
65
+ class RateLimitError < HttpError
66
+ # @return [Float, nil] seconds the API asked to wait before retrying, from the +Retry-After+ header
67
+ attr_reader :retry_after
68
+
69
+ # @param message [String] the error message
70
+ # @param status [Integer] the HTTP status of the response
71
+ # @param response_body [String] the raw response body
72
+ # @param retry_after [Float, nil] seconds to wait before retrying
73
+ def initialize(message, status:, response_body:, retry_after: nil)
74
+ super(message, status: status, response_body: response_body)
75
+ @retry_after = retry_after
76
+ end
77
+ end
78
+
79
+ # The API answered with HTTP 401 or 403: check the API token and secret.
80
+ class UnauthorizedError < HttpError; end
81
+
82
+ # The API answered with an HTTP 5xx status. Usually transient.
83
+ class ServerError < HttpError; end
84
+
85
+ # The API answered with a 2xx status, but the SDK could not read the response.
86
+ class InvalidResponseError < Error
87
+ # @return [Integer, nil] the HTTP status of the response, +nil+ when the body was rejected before the status was
88
+ # known
89
+ attr_reader :status
90
+
91
+ # @return [String] the raw response body, empty when it was too large to keep
92
+ attr_reader :response_body
93
+
94
+ # @param message [String] the error message
95
+ # @param status [Integer, nil] the HTTP status of the response
96
+ # @param response_body [String] the raw response body
97
+ def initialize(message, status:, response_body:)
98
+ super(message)
99
+ @status = status
100
+ @response_body = response_body
101
+ end
102
+ end
103
+
104
+ # No response arrived: the request failed below the HTTP level.
105
+ class NetworkError < Error; end
106
+
107
+ # The connection could not be established: the host name did not resolve or the connection was refused.
108
+ class ConnectionError < NetworkError; end
109
+
110
+ # The request did not complete within the configured timeout.
111
+ class TimeoutError < NetworkError; end
112
+
113
+ # The TLS handshake failed, for example because the server certificate is not trusted.
114
+ class SslError < NetworkError; end
115
+
116
+ # Error codes the TronZap API reports in {ApiError#code}.
117
+ module ErrorCode
118
+ # Authentication error: check the API token and that the signature is calculated correctly.
119
+ AUTH_ERROR = 1
120
+ # Invalid service or parameters: check the service name and the parameters.
121
+ INVALID_SERVICE_OR_PARAMS = 2
122
+ # Wallet not found: verify the wallet address, or contact support if you believe this is an error.
123
+ WALLET_NOT_FOUND = 5
124
+ # Insufficient funds: top up the account or request a smaller amount.
125
+ INSUFFICIENT_FUNDS = 6
126
+ # Invalid TRON address: it should be a valid 34-character TRON address.
127
+ INVALID_TRON_ADDRESS = 10
128
+ # Invalid energy amount.
129
+ INVALID_ENERGY_AMOUNT = 11
130
+ # Invalid duration.
131
+ INVALID_DURATION = 12
132
+ # Transaction or subscription not found: check the transaction ID or external ID. The API reports this code
133
+ # under the key +subscription_not_found+.
134
+ TRANSACTION_NOT_FOUND = 20
135
+ # The subscription cannot be stopped right now.
136
+ CANNOT_STOP_SUBSCRIPTION = 21
137
+ # Address not activated: activate it first with an address activation transaction.
138
+ ADDRESS_NOT_ACTIVATED = 24
139
+ # Address already activated. No action is needed.
140
+ ADDRESS_ALREADY_ACTIVATED = 25
141
+ # AML check not found: check the ID or run the AML check again.
142
+ AML_CHECK_NOT_FOUND = 30
143
+ # The service is temporarily unavailable.
144
+ SERVICE_NOT_AVAILABLE = 35
145
+ # Invalid bandwidth amount.
146
+ INVALID_BANDWIDTH_AMOUNT = 50
147
+ # Internal server error. Contact support if it persists.
148
+ INTERNAL_SERVER_ERROR = 500
149
+ end
150
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "openssl"
5
+ require "zlib"
6
+
7
+ module Tronzap
8
+ # The transport a {Client} sends requests through.
9
+ #
10
+ # An adapter is any object with a +call+ method that takes a {Request} and returns a {Response} (or any object
11
+ # with +status+, +headers+ and +body+). The client builds the body, signs it and parses the response; the adapter
12
+ # only moves bytes. {NetHttp} is the default.
13
+ #
14
+ # Failures raised by an adapter are reported as {NetworkError} subclasses when they are standard Ruby network
15
+ # errors: +Timeout::Error+, +OpenSSL::SSL::SSLError+, +SocketError+, +SystemCallError+, +IOError+ and the
16
+ # +Net::HTTP+ protocol errors. An adapter built on another HTTP library should rescue that library's errors and
17
+ # raise {TimeoutError}, {ConnectionError}, {SslError} or {NetworkError} itself.
18
+ #
19
+ # @example An adapter on top of Faraday
20
+ # class FaradayAdapter
21
+ # def initialize(connection) = @connection = connection
22
+ #
23
+ # def call(request)
24
+ # response = @connection.post(request.url, request.body, request.headers) do |req|
25
+ # req.options.timeout = request.timeout
26
+ # end
27
+ # Tronzap::HttpAdapter::Response.new(status: response.status, headers: response.headers.to_h,
28
+ # body: response.body.to_s)
29
+ # rescue Faraday::TimeoutError => e
30
+ # raise Tronzap::TimeoutError, e.message
31
+ # rescue Faraday::SSLError => e
32
+ # raise Tronzap::SslError, e.message
33
+ # rescue Faraday::ConnectionFailed => e
34
+ # raise Tronzap::ConnectionError, e.message
35
+ # end
36
+ # end
37
+ module HttpAdapter
38
+ # An HTTP request to send.
39
+ #
40
+ # @!attribute [r] http_method
41
+ # @return [Symbol] the HTTP method, always +:post+
42
+ # @!attribute [r] url
43
+ # @return [String] the absolute URL
44
+ # @!attribute [r] headers
45
+ # @return [Hash{String => String}] the request headers
46
+ # @!attribute [r] body
47
+ # @return [String] the JSON body. It is signed byte for byte, so send it unchanged.
48
+ # @!attribute [r] timeout
49
+ # @return [Numeric] seconds allowed for connecting and for each read or write
50
+ Request = Data.define(:http_method, :url, :headers, :body, :timeout)
51
+
52
+ # An HTTP response.
53
+ #
54
+ # @!attribute [r] status
55
+ # @return [Integer] the HTTP status
56
+ # @!attribute [r] headers
57
+ # @return [Hash{String => String}] the response headers; names are matched case-insensitively
58
+ # @!attribute [r] body
59
+ # @return [String] the response body
60
+ Response = Data.define(:status, :headers, :body)
61
+
62
+ # The largest response body the default adapter reads.
63
+ MAX_RESPONSE_BYTES = 8 * 1024 * 1024
64
+
65
+ # The default adapter, on top of +Net::HTTP+ from the standard library.
66
+ #
67
+ # It opens a new connection for every request and keeps no state between requests, so it is safe to share
68
+ # across threads. TLS certificates are always verified. Proxies are taken from the +http_proxy+ and
69
+ # +https_proxy+ environment variables.
70
+ class NetHttp
71
+ # @param ca_file [String, nil] a PEM file with the certificates to trust instead of the system store
72
+ def initialize(ca_file: nil)
73
+ @ca_file = ca_file&.dup&.freeze
74
+ freeze
75
+ end
76
+
77
+ # @param request [Request]
78
+ # @return [Response]
79
+ def call(request)
80
+ uri = URI.parse(request.url)
81
+ http = connection(uri, request.timeout)
82
+ post = Net::HTTP::Post.new(uri.request_uri, request.headers)
83
+ post.body = request.body
84
+
85
+ http.start do
86
+ http.request(post) do |response|
87
+ return Response.new(status: response.code.to_i, headers: headers(response), body: read(response))
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def connection(uri, timeout)
95
+ http = Net::HTTP.new(uri.host, uri.port)
96
+ http.use_ssl = uri.scheme == "https"
97
+ if http.use_ssl?
98
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
99
+ http.ca_file = @ca_file if @ca_file
100
+ end
101
+ http.open_timeout = timeout
102
+ http.read_timeout = timeout
103
+ http.write_timeout = timeout
104
+ http.ssl_timeout = timeout
105
+ http.max_retries = 0
106
+ http
107
+ end
108
+
109
+ def headers(response)
110
+ response.each_header.to_h
111
+ end
112
+
113
+ def read(response)
114
+ status = response.code.to_i
115
+ raise too_large(status) if response.content_length.to_i > MAX_RESPONSE_BYTES
116
+
117
+ body = +""
118
+ response.read_body do |chunk|
119
+ raise too_large(status) if body.bytesize + chunk.bytesize > MAX_RESPONSE_BYTES
120
+
121
+ body << chunk
122
+ end
123
+ body
124
+ end
125
+
126
+ def too_large(status)
127
+ InvalidResponseError.new("Response body exceeds #{MAX_RESPONSE_BYTES} bytes",
128
+ status: status, response_body: "")
129
+ end
130
+ end
131
+
132
+ CONNECTION_ERRORS = [
133
+ SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::EADDRNOTAVAIL
134
+ ].freeze
135
+ private_constant :CONNECTION_ERRORS
136
+
137
+ NETWORK_ERRORS = [
138
+ OpenSSL::SSL::SSLError, Timeout::Error, IOError, SystemCallError, Net::ProtocolError, Net::HTTPBadResponse,
139
+ Zlib::Error, *CONNECTION_ERRORS
140
+ ].freeze
141
+ private_constant :NETWORK_ERRORS
142
+
143
+ # Runs the block and reports standard Ruby network failures as {NetworkError} subclasses.
144
+ #
145
+ # @api private
146
+ def self.translate_errors
147
+ yield
148
+ rescue Tronzap::Error
149
+ raise
150
+ rescue *NETWORK_ERRORS => e
151
+ raise classify(e)
152
+ end
153
+
154
+ def self.classify(error)
155
+ case error
156
+ when OpenSSL::SSL::SSLError
157
+ SslError.new("TLS error: #{error.message}")
158
+ when Timeout::Error, Errno::ETIMEDOUT
159
+ TimeoutError.new("The request timed out: #{error.message}")
160
+ when *CONNECTION_ERRORS
161
+ ConnectionError.new("Connection failed: #{error.message}")
162
+ else
163
+ NetworkError.new("Network error: #{error.class}: #{error.message}")
164
+ end
165
+ end
166
+ private_class_method :classify
167
+ end
168
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tronzap
4
+ module Models
5
+ # The price of activating a TRON address.
6
+ #
7
+ # @!attribute [r] price
8
+ # @return [BigDecimal] activation price, in TRX
9
+ ActivateAddressRate = Data.define(:price)
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tronzap
4
+ module Models
5
+ # The resources a TRON address has available.
6
+ #
7
+ # @!attribute [r] energy
8
+ # @return [Integer] available energy
9
+ # @!attribute [r] bandwidth
10
+ # @return [Integer] available bandwidth
11
+ AddressResources = Data.define(:energy, :bandwidth)
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tronzap
4
+ module Models
5
+ # One factor that contributed to an AML risk score.
6
+ #
7
+ # @!attribute [r] name
8
+ # @return [String] machine-readable factor name
9
+ # @!attribute [r] label
10
+ # @return [String] human-readable factor label
11
+ # @!attribute [r] group
12
+ # @return [String] the group the factor belongs to
13
+ # @!attribute [r] score
14
+ # @return [BigDecimal] the factor's share of the risk score
15
+ AmlRiskFactor = Data.define(:name, :label, :group, :score) do
16
+ # @api private
17
+ def self.from_api(value)
18
+ data = Coerce.object(value, "risk factor")
19
+ new(
20
+ name: Coerce.text(data["name"], "name"),
21
+ label: Coerce.text(data["label"], "label"),
22
+ group: Coerce.text(data["group"], "group"),
23
+ score: Coerce.decimal(data["score"], "score")
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tronzap
4
+ module Models
5
+ # A bandwidth price tier. Bandwidth is priced per 1000 units: 345 bandwidth at a {price} of 1 costs 0.345.
6
+ #
7
+ # @!attribute [r] duration
8
+ # @return [Integer] rental duration in hours
9
+ # @!attribute [r] min_amount
10
+ # @return [Integer] smallest bandwidth amount this tier applies to
11
+ # @!attribute [r] max_amount
12
+ # @return [Integer] largest bandwidth amount this tier applies to
13
+ # @!attribute [r] price
14
+ # @return [BigDecimal] price per 1000 units of bandwidth, in TRX
15
+ BandwidthRate = Data.define(:duration, :min_amount, :max_amount, :price) do
16
+ # @api private
17
+ def self.from_api(value)
18
+ data = Coerce.object(value, "bandwidth rate")
19
+ new(
20
+ duration: Coerce.integer(data["duration"], "duration"),
21
+ min_amount: Coerce.integer(data["min_amount"], "min_amount"),
22
+ max_amount: Coerce.integer(data["max_amount"], "max_amount"),
23
+ price: Coerce.decimal(data["price"], "price")
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end