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
data/README.md ADDED
@@ -0,0 +1,392 @@
1
+ # Tron Energy Rental via API
2
+ ## Ruby SDK by TronZap.com
3
+
4
+ **[English](README.md)** | [Español](README.es.md) | [Português](README.pt-br.md) | [Русский](README.ru.md)
5
+
6
+ [![Gem Version](https://img.shields.io/gem/v/tronzap-sdk.svg)](https://rubygems.org/gems/tronzap-sdk)
7
+ [![CI](https://github.com/tron-energy-market/tronzap-sdk-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/tron-energy-market/tronzap-sdk-ruby/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
+
10
+ Official Ruby SDK for the TronZap API.
11
+ This SDK allows you to easily integrate with TronZap services for TRON energy rental.
12
+
13
+ TronZap.com allows you to [buy TRON energy](https://tronzap.com/), making USDT (TRC20) transfers cheaper by significantly reducing transaction fees.
14
+
15
+ 👉 [Register for an API key](https://tronzap.com) to start using TronZap API and integrate it via the SDK.
16
+
17
+ - Website: https://tronzap.com/
18
+ - API reference: https://docs.tronzap.com/
19
+ - RubyGems: https://rubygems.org/gems/tronzap-sdk
20
+ - Source: https://github.com/tron-energy-market/tronzap-sdk-ruby
21
+
22
+ ## Installation
23
+
24
+ Add the gem to your Gemfile:
25
+
26
+ ```ruby
27
+ gem "tronzap-sdk"
28
+ ```
29
+
30
+ or install it directly:
31
+
32
+ ```bash
33
+ gem install tronzap-sdk
34
+ ```
35
+
36
+ ## Requirements
37
+
38
+ - Ruby 3.3 or newer
39
+ - One runtime dependency, `bigdecimal`. The SDK does not depend on Rails.
40
+
41
+ ## Quick start
42
+
43
+ ```ruby
44
+ require "tronzap"
45
+
46
+ client = Tronzap::Client.new(
47
+ api_token: "your_api_token",
48
+ api_secret: "your_api_secret"
49
+ )
50
+
51
+ begin
52
+ balance = client.get_balance
53
+ puts "balance: #{balance.balance.to_s("F")} (deposit to #{balance.address})"
54
+
55
+ # Estimate how much energy a USDT transfer needs, then buy exactly that much.
56
+ estimate = client.estimate_energy(from_address: "TSenderAddress", to_address: "TRecipientAddress")
57
+
58
+ transaction = client.create_energy_transaction(
59
+ address: "TRecipientAddress",
60
+ energy: estimate.energy,
61
+ duration: 1,
62
+ external_id: "order-42",
63
+ activate_address: true
64
+ )
65
+ puts "transaction #{transaction.id} costs #{transaction.amount.to_s("F")} and is #{transaction.status}"
66
+ rescue Tronzap::Error => e
67
+ warn "TronZap call failed: #{e.message}"
68
+ end
69
+ ```
70
+
71
+ `require "tronzap"` loads the SDK. With Bundler, `gem "tronzap-sdk"` in the Gemfile loads it as well.
72
+
73
+ A runnable walkthrough of every operation lives in
74
+ [`examples/basic_usage.rb`](examples/basic_usage.rb):
75
+
76
+ ```bash
77
+ export TRONZAP_API_TOKEN=your_api_token
78
+ export TRONZAP_API_SECRET=your_api_secret
79
+ export TRONZAP_BASE_URL=api.tronzap.com # optional
80
+ ruby -Ilib examples/basic_usage.rb
81
+ ```
82
+
83
+ By default it only reads and spends nothing. Setting `TRONZAP_ALLOW_PURCHASES=1`
84
+ also exercises the endpoints that create transactions and AML checks, which debit
85
+ the account balance. See the comment at the top of the file for the other optional
86
+ variables.
87
+
88
+ ## Configuration
89
+
90
+ The client takes the two credentials from your dashboard: the API token is sent as
91
+ a bearer token, and the API secret signs every request body and is never sent.
92
+ Everything else is optional. Pass the settings as keyword arguments, in a block,
93
+ or both; the block runs last:
94
+
95
+ ```ruby
96
+ client = Tronzap::Client.new(
97
+ api_token: api_token,
98
+ api_secret: api_secret,
99
+ base_url: "api.tronzap.com", # defaults to Tronzap::Configuration::DEFAULT_BASE_URL
100
+ timeout: 10, # seconds; defaults to 30
101
+ user_agent: "my-app/1.0"
102
+ )
103
+
104
+ client = Tronzap::Client.new do |config|
105
+ config.api_token = ENV.fetch("TRONZAP_API_TOKEN")
106
+ config.api_secret = ENV.fetch("TRONZAP_API_SECRET")
107
+ config.timeout = 10
108
+ end
109
+ ```
110
+
111
+ `base_url` takes either a bare domain or a full URL: a missing scheme becomes
112
+ `https` and a trailing slash is trimmed, so `"api.tronzap.com"`,
113
+ `"api.tronzap.com/"` and `"https://api.tronzap.com"` are equivalent. Pass an
114
+ explicit scheme to opt out, for example `"http://localhost:8080"` against a local
115
+ mock.
116
+
117
+ `timeout` applies to opening the connection and to each read and write, not to
118
+ the request as a whole.
119
+
120
+ The SDK keeps no global state. Each client validates its settings when it is
121
+ created and freezes them, so a client is immutable and safe to share between
122
+ threads. Create one per set of credentials. `inspect` never shows the
123
+ credentials.
124
+
125
+ ### Your own HTTP adapter
126
+
127
+ The default adapter uses `Net::HTTP` from the standard library, opens a new
128
+ connection for every request, always verifies TLS certificates, and honours the
129
+ `https_proxy` environment variable. To trust a private certificate authority, pass
130
+ its PEM file:
131
+
132
+ ```ruby
133
+ adapter = Tronzap::HttpAdapter::NetHttp.new(ca_file: "/etc/ssl/corporate-ca.pem")
134
+ client = Tronzap::Client.new(api_token: api_token, api_secret: api_secret, adapter: adapter)
135
+ ```
136
+
137
+ Any object with a `call` method can replace it. It receives a
138
+ `Tronzap::HttpAdapter::Request` (`http_method`, `url`, `headers`, `body`,
139
+ `timeout`) and returns a `Tronzap::HttpAdapter::Response` (`status`, `headers`,
140
+ `body`). Send the body unchanged: it is signed byte for byte.
141
+
142
+ ```ruby
143
+ class FaradayAdapter
144
+ def initialize(connection)
145
+ @connection = connection
146
+ end
147
+
148
+ def call(request)
149
+ response = @connection.post(request.url, request.body, request.headers) do |req|
150
+ req.options.timeout = request.timeout
151
+ end
152
+ Tronzap::HttpAdapter::Response.new(status: response.status, headers: response.headers.to_h,
153
+ body: response.body.to_s)
154
+ rescue Faraday::TimeoutError => e
155
+ raise Tronzap::TimeoutError, e.message
156
+ rescue Faraday::SSLError => e
157
+ raise Tronzap::SslError, e.message
158
+ rescue Faraday::ConnectionFailed => e
159
+ raise Tronzap::ConnectionError, e.message
160
+ end
161
+ end
162
+ ```
163
+
164
+ Standard Ruby network errors raised by an adapter (`Timeout::Error`,
165
+ `OpenSSL::SSL::SSLError`, `SocketError`, `SystemCallError`, `IOError`) are
166
+ reported as `Tronzap::NetworkError` subclasses automatically. Errors of other HTTP
167
+ libraries need to be translated by the adapter, as above.
168
+
169
+ ## Available methods
170
+
171
+ | Method | Endpoint | Description |
172
+ |---|---|---|
173
+ | `get_services` | `/v1/services` | Available services and prices |
174
+ | `get_balance` | `/v1/balance` | Current account balance |
175
+ | `get_address_info(address)` | `/v1/address-info` | Address resources (energy, bandwidth) and balances (TRX, USDT) |
176
+ | `estimate_energy(from_address:, to_address:, contract_address: nil)` | `/v1/estimate-energy` | Energy a transfer needs, and its cost |
177
+ | `calculate(address:, energy:, duration: 1)` | `/v1/calculate` | Price a purchase without creating a transaction |
178
+ | `create_energy_transaction(address:, energy:, duration: 1, external_id: nil, activate_address: false)` | `/v1/transaction/new` | Buy energy |
179
+ | `create_bandwidth_transaction(address:, bandwidth:, external_id: nil)` | `/v1/transaction/new` | Buy bandwidth |
180
+ | `create_resource_bundle_transaction(address:, energy:, bandwidth:, duration: 1, external_id: nil, activate_address: false)` | `/v1/transaction/new` | Buy energy and bandwidth in one transaction |
181
+ | `create_address_activation_transaction(address:, external_id: nil)` | `/v1/transaction/new` | Activate a TRON address |
182
+ | `check_transaction(id: nil, external_id: nil)` | `/v1/transaction/check` | Status of a transaction, by id or external id |
183
+ | `get_direct_recharge_info` | `/v1/direct-recharge-info` | Direct recharge address and rates |
184
+ | `get_aml_services` | `/v1/aml-checks` | AML services and pricing |
185
+ | `create_aml_check(type:, network:, address:, transaction_hash: nil, direction: nil)` | `/v1/aml-checks/new` | Start an AML screening |
186
+ | `check_aml_status(id)` | `/v1/aml-checks/check` | Status and result of an AML check |
187
+ | `get_aml_history(page: 1, per_page: 10, status: nil)` | `/v1/aml-checks/history` | Paginated AML check history |
188
+
189
+ Methods with parameters take either keyword arguments or a request object from
190
+ `Tronzap::Requests`, so a request can be built, validated and passed around before
191
+ it is sent:
192
+
193
+ ```ruby
194
+ request = Tronzap::Requests::EnergyTransaction.new(address: "TRecipientAddress", energy: 65000)
195
+ client.create_energy_transaction(request)
196
+ ```
197
+
198
+ A request is validated when it is created, so an invalid one raises
199
+ `ArgumentError` and never reaches the API. Amounts must be positive `Integer`s.
200
+ Defaults match the API: `duration` is 1 hour, and AML history starts at page 1
201
+ with 10 items.
202
+
203
+ Results are immutable `Data` objects in `Tronzap::Responses` and
204
+ `Tronzap::Models`, not hashes: `transaction.status`, `estimate.energy`. Collections
205
+ are frozen and never `nil`, and values the API may omit are `nil`.
206
+
207
+ ### Buying resources
208
+
209
+ ```ruby
210
+ # Energy, optionally activating the address in the same call.
211
+ client.create_energy_transaction(
212
+ address: "TRecipientAddress",
213
+ energy: 65000,
214
+ duration: 1, # hours; see get_services for the durations on sale
215
+ external_id: "order-42",
216
+ activate_address: true
217
+ )
218
+
219
+ # Bandwidth.
220
+ client.create_bandwidth_transaction(address: "TRecipientAddress", bandwidth: 345, external_id: "bandwidth-1")
221
+
222
+ # Energy and bandwidth together in one transaction.
223
+ client.create_resource_bundle_transaction(
224
+ address: "TRecipientAddress",
225
+ energy: 65000,
226
+ bandwidth: 345,
227
+ external_id: "bundle-1"
228
+ )
229
+
230
+ # Activation on its own.
231
+ client.create_address_activation_transaction(address: "TRecipientAddress", external_id: "activation-1")
232
+ ```
233
+
234
+ Energy prices are per unit, bandwidth prices are per 1000 units: in
235
+ `get_services`, `EnergyRate#price` × 65000 is the cost of 65000 energy, while 345
236
+ bandwidth at a `BandwidthRate#price` of 1 costs 0.345.
237
+
238
+ The API currently reports a resource bundle with `service` equal to `:energy`, not
239
+ `:resource_bundle`. Read `params.amounts` to see which resources a transaction
240
+ contains.
241
+
242
+ ### Following a transaction
243
+
244
+ A transaction moves through `:new` → `:pending` → `:success` or `:failed`:
245
+
246
+ ```ruby
247
+ transaction = nil
248
+ loop do
249
+ sleep 2
250
+ transaction = client.check_transaction(external_id: "order-42")
251
+ break unless %i[new pending].include?(transaction.status)
252
+ end
253
+
254
+ puts "finished as #{transaction.status}, hash #{transaction.transaction_hash || "none"}"
255
+ ```
256
+
257
+ ### AML screening
258
+
259
+ ```ruby
260
+ check = client.create_aml_check(Tronzap::Requests::AmlCheck.for_address("TRX", "TAddressToScreen"))
261
+ # or Tronzap::Requests::AmlCheck.for_hash("BTC", "bc1RecipientAddress", "TX_HASH", direction: :withdrawal)
262
+
263
+ result = client.check_aml_status(check.id)
264
+ if result.status == :completed
265
+ puts "#{result.risk_level} #{result.risk_score.to_s("F")} #{result.risk_factors.size} factor(s)"
266
+ end
267
+ ```
268
+
269
+ `risk_score` is `nil` until screening finishes. A completed check can have a score
270
+ of 0, which is not the same as having no score yet.
271
+
272
+ ## Error handling
273
+
274
+ Every failure of an API call is a `Tronzap::Error`. Rescue a subclass to handle
275
+ one kind of failure:
276
+
277
+ ```
278
+ Tronzap::Error
279
+ ├── Tronzap::ApiError — the API answered with a non-zero code
280
+ ├── Tronzap::HttpError — non-2xx response without an API payload
281
+ │ ├── Tronzap::RateLimitError — HTTP 429
282
+ │ ├── Tronzap::UnauthorizedError — HTTP 401 or 403
283
+ │ └── Tronzap::ServerError — HTTP 5xx
284
+ ├── Tronzap::InvalidResponseError — 2xx response the SDK could not read
285
+ └── Tronzap::NetworkError — no response arrived
286
+ ├── Tronzap::ConnectionError — DNS failure, connection refused
287
+ ├── Tronzap::TimeoutError — the request exceeded its timeout
288
+ └── Tronzap::SslError — TLS handshake or certificate failure
289
+ ```
290
+
291
+ `ApiError`, `HttpError` and `InvalidResponseError` carry the HTTP status
292
+ (`status`) and the raw response body (`response_body`). `ApiError` also carries
293
+ the API error code (`code`), the error key (`error_key`) and the request ID
294
+ (`request_id`). `RateLimitError#retry_after` holds the `Retry-After` delay in
295
+ seconds when the API sends one.
296
+
297
+ Invalid arguments are not API failures: they raise `ArgumentError` before anything
298
+ is sent.
299
+
300
+ ```ruby
301
+ begin
302
+ client.create_energy_transaction(address: "TRecipientAddress", energy: 65000)
303
+ rescue Tronzap::ApiError => e
304
+ # Application-level failure: the code says exactly what went wrong.
305
+ case e.code
306
+ when Tronzap::ErrorCode::INVALID_TRON_ADDRESS
307
+ # The key may narrow it down, e.g. "invalid_tron_address.from_address"
308
+ warn "bad address: #{e.error_key}"
309
+ when Tronzap::ErrorCode::INSUFFICIENT_FUNDS
310
+ warn "top up the account"
311
+ when Tronzap::ErrorCode::ADDRESS_NOT_ACTIVATED
312
+ warn "activate the address first"
313
+ else
314
+ warn "api error #{e.code}: #{e.message} (request #{e.request_id || "-"})"
315
+ end
316
+ rescue Tronzap::RateLimitError => e
317
+ # Back off and retry, after e.retry_after seconds if the API sent it.
318
+ rescue Tronzap::UnauthorizedError
319
+ # Bad token or signature.
320
+ rescue Tronzap::TimeoutError, Tronzap::ServerError
321
+ # Transient; safe to retry.
322
+ rescue Tronzap::NetworkError
323
+ # Unreachable.
324
+ end
325
+ ```
326
+
327
+ `request_id` is the identifier the API assigns to each request. Quote it when
328
+ contacting support.
329
+
330
+ An API error takes precedence over the HTTP status: the API reports some failures
331
+ with a 2xx status and others with a 4xx or 5xx status, so a readable payload with
332
+ a non-zero code is always reported as `Tronzap::ApiError`, never as
333
+ `Tronzap::HttpError`.
334
+
335
+ ### API error codes
336
+
337
+ | Code | Constant | Description |
338
+ |------|----------|-------------|
339
+ | 1 | `AUTH_ERROR` | Authentication error – invalid API token or signature |
340
+ | 2 | `INVALID_SERVICE_OR_PARAMS` | Invalid service or parameters |
341
+ | 5 | `WALLET_NOT_FOUND` | Internal wallet not found. Contact support. |
342
+ | 6 | `INSUFFICIENT_FUNDS` | Insufficient funds |
343
+ | 10 | `INVALID_TRON_ADDRESS` | Invalid TRON address |
344
+ | 11 | `INVALID_ENERGY_AMOUNT` | Invalid energy amount |
345
+ | 12 | `INVALID_DURATION` | Invalid duration |
346
+ | 20 | `TRANSACTION_NOT_FOUND` | Transaction/subscription not found |
347
+ | 21 | `CANNOT_STOP_SUBSCRIPTION` | Cannot stop subscription |
348
+ | 24 | `ADDRESS_NOT_ACTIVATED` | Address not activated |
349
+ | 25 | `ADDRESS_ALREADY_ACTIVATED` | Address already activated |
350
+ | 30 | `AML_CHECK_NOT_FOUND` | AML check not found |
351
+ | 35 | `SERVICE_NOT_AVAILABLE` | Service not available |
352
+ | 50 | `INVALID_BANDWIDTH_AMOUNT` | Invalid bandwidth amount |
353
+ | 500 | `INTERNAL_SERVER_ERROR` | Internal server error – contact support |
354
+
355
+ The constants live in `Tronzap::ErrorCode`. A code this SDK version does not know
356
+ is still available as a number from `ApiError#code`.
357
+
358
+ ## Decimal and timestamp fields
359
+
360
+ Amounts and prices are `BigDecimal`, so they keep the exact value the API sent.
361
+ The API encodes money as a JSON number in some responses and as a JSON string in
362
+ others; both forms are read the same way. Use `to_s("F")` to print one without
363
+ exponent notation.
364
+
365
+ Timestamps are `Tronzap::Models::Timestamp` objects: `value` is the parsed `Time`
366
+ and `raw` is the text exactly as the API sent it. The several formats the API
367
+ emits are accepted, and times without an offset are read as UTC. An unrecognised
368
+ timestamp leaves `value` as `nil` instead of failing the whole response.
369
+
370
+ Enum-like fields are symbols, such as `:energy` or `:completed`. A value the API
371
+ may add in the future, such as a new transaction status, is reported as `:unknown`
372
+ instead of failing. The known values are listed in `Tronzap::Models`.
373
+
374
+ ## Testing
375
+
376
+ ```bash
377
+ bundle install
378
+ bundle exec rspec
379
+ bundle exec rubocop
380
+ ```
381
+
382
+ The specs run against a local HTTP server: the exact request body and signature of
383
+ every endpoint, API and HTTP errors, malformed JSON, timeouts, network and TLS
384
+ failures, and concurrent use.
385
+
386
+ ## License
387
+
388
+ The MIT License (MIT). Please see [License File](LICENSE) for more information.
389
+
390
+ ## Support
391
+
392
+ For support, please contact [support@tronzap.com](mailto:support@tronzap.com).