tron.rb 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cb06efba2988905e0c66fa13f78960c58b91b08646a1822ad1878182ad359f90
4
+ data.tar.gz: f3f3b0b7b88280f7fcbc5733b57d752abef9a8623ea7a05a360e91c790131f33
5
+ SHA512:
6
+ metadata.gz: 6fcef166d0c652dcb4deaf3040eb71f9647009e315d53486adbfd505bc322848d9516f6d60d72f3623bc4fc11b11fe6d58798544df7b566f9782828add54d501
7
+ data.tar.gz: deb26afaafb9c7dc395dabcb17ac31a4ff01c1458f61d6e3db394659d1c1ad2daf6e2a7a82ed4f2e59d6efb61776d178e1e38254aa2e5b978da6b77064f400fe
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ScionX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS OF A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # TRON Ruby Client
2
+
3
+ A Ruby gem for interacting with the TRON blockchain to check wallet balances and related information.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install tron.rb
9
+ ```
10
+
11
+ Or add to your Gemfile:
12
+
13
+ ```ruby
14
+ gem 'tron.rb'
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ You can configure the client using environment variables or programmatically:
20
+
21
+ #### Using Environment Variables:
22
+ ```bash
23
+ export TRONGRID_API_KEY=your_api_key
24
+ export TRONSCAN_API_KEY=your_tronscan_api_key
25
+ ```
26
+
27
+ #### Using Code:
28
+ ```ruby
29
+ require 'tron'
30
+
31
+ Tron.configure do |config|
32
+ config.api_key = 'your_trongrid_api_key'
33
+ config.tronscan_api_key = 'your_tronscan_api_key'
34
+ config.network = :mainnet # or :shasta or :nile
35
+ config.timeout = 30
36
+ end
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Command Line
42
+
43
+ ```bash
44
+ # Use the CLI tool to check wallet balances
45
+ ruby bin/tron-wallet TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb
46
+ ```
47
+
48
+ ### As a Library
49
+
50
+ #### Initialize Client:
51
+ ```ruby
52
+ require 'tron'
53
+
54
+ # Use default configuration (loads from ENV)
55
+ client = Tron::Client.new
56
+
57
+ # Or with custom configuration
58
+ client = Tron::Client.new(
59
+ api_key: 'your_trongrid_api',
60
+ tronscan_api_key: 'your_tronscan_api',
61
+ network: :mainnet,
62
+ timeout: 30
63
+ )
64
+ ```
65
+
66
+ #### Get TRX Balance:
67
+ ```ruby
68
+ trx_balance = client.balance_service.get_trx('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
69
+ ```
70
+
71
+ #### Get TRC20 Token Balances:
72
+ ```ruby
73
+ tokens = client.balance_service.get_trc20_tokens('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
74
+ ```
75
+
76
+ #### Get Account Resources:
77
+ ```ruby
78
+ resources = client.resources_service.get('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
79
+ ```
80
+
81
+ #### Get Token Prices:
82
+ ```ruby
83
+ price = client.price_service.get_token_price('trx')
84
+ usd_price = client.price_service.get_token_price_usd('trx')
85
+ ```
86
+
87
+ #### Get Complete Wallet Information:
88
+ ```ruby
89
+ wallet_info = client.get_wallet_balance('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
90
+ full_info = client.get_full_account_info('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
91
+ ```
92
+
93
+ ## Features
94
+
95
+ - ✓ TRX balance
96
+ - ✓ TRC20 token balances (USDT, USDC, USDD, TUSD, WBTC)
97
+ - ✓ Account resources (bandwidth & energy)
98
+ - ✓ Token prices
99
+ - ✓ Portfolio tracking
100
+ - ✓ Multi-network support (mainnet, shasta, nile)
101
+ - ✓ Clean, simple output
102
+ - ✓ Proper decimal formatting
103
+ - ✓ Environment variable support
104
+ - ✓ Rate limit management with API keys
105
+
106
+ ## Services Architecture
107
+
108
+ The gem is organized into modular services:
109
+
110
+ - `Tron::Services::Balance` - TRX and TRC20 token balances
111
+ - `Tron::Services::Resources` - Account resources (bandwidth/energy)
112
+ - `Tron::Services::Price` - Token price information
113
+ - `Tron::Utils::HTTP` - HTTP client with error handling
114
+ - `Tron::Utils::Address` - TRON address validation and conversion
115
+
116
+ ## API Reference
117
+
118
+ ### Balance Service
119
+ - `get_trx(address)` - Get TRX balance
120
+ - `get_trc20_tokens(address)` - Get all TRC20 token balances
121
+ - `get_all(address)` - Get all balance information
122
+
123
+ ### Resources Service
124
+ - `get(address)` - Get account resources (bandwidth, energy)
125
+
126
+ ### Price Service
127
+ - `get_token_price(token)` - Get price information for a token
128
+ - `get_all_prices()` - Get prices for all tokens
129
+ - `get_token_price_usd(token)` - Get price in USD
130
+ - `get_token_value_usd(balance, token)` - Calculate dollar value
131
+ - `get_multiple_token_prices(tokens)` - Get multiple token prices
132
+ - `format_price(price, currency)` - Format price with currency
133
+
134
+ ## Output Example
135
+
136
+ ```
137
+ ════════════════════════════════════════════════════════════
138
+ TRON WALLET BALANCE CHECKER
139
+ ════════════════════════════════════════════════════════════
140
+ Wallet: TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb
141
+
142
+ TRX Balance:
143
+ 1234.567890 TRX
144
+
145
+ TRC20 Token Balances:
146
+ USDT 100.000000
147
+ USDC 50.500000
148
+
149
+ Account Resources:
150
+ Bandwidth: 1,500 / 5,000
151
+ Energy: 10,000 / 50,000
152
+
153
+ ════════════════════════════════════════════════════════════
154
+ ```
155
+
156
+ ## Environment Variables
157
+
158
+ - `TRONGRID_API_KEY` - TronGrid API key (optional, increases rate limits)
159
+ - `TRONSCAN_API_KEY` - Tronscan API key (optional, increases rate limits)
160
+
161
+ ## Getting an API Key
162
+
163
+ 1. Visit [TronGrid](https://www.trongrid.io/)
164
+ 2. Sign up for a free account
165
+ 3. Get your API key from the dashboard
166
+ 4. Use it in your `.env` file or as an environment variable
167
+
168
+ ## Popular Token Addresses
169
+
170
+ - USDT: `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`
171
+ - USDC: `TEkxiTehnzSmAaVPYYJNTY7v1KHVqCvRdx`
172
+ - USDD: `TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn`
173
+ - TUSD: `TUpMhErZL2fhh4sVNULzmL7sbb8NkK57eX`
174
+ - WBTC: `TXpw8XeWYeTUd4quDskoUqeQPowRh4jY65`
175
+
176
+ ## Contributing
177
+
178
+ We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This enables automated release notes and semantic versioning.
179
+ We use Minitest for testing.
180
+
181
+ ### Commit Message Format
182
+ - `fix: ...` for bug fixes (triggers PATCH release)
183
+ - `feat: ...` for new features (triggers MINOR release)
184
+ - `feat!: ...` or `BREAKING CHANGE: ...` for breaking changes (triggers MAJOR release)
185
+
186
+ ### Running Tests
187
+ ```bash
188
+ bundle install
189
+ bundle exec rake test
190
+ ```
191
+
192
+ ## Automated Releases
193
+
194
+ This project uses GitHub Actions to automate releases:
195
+ 1. Commits following Conventional Commits format trigger release PRs
196
+ 2. Release PRs are automatically created and must be manually reviewed and merged
197
+ 3. Once merged to master, the gem is automatically built and published to RubyGems
198
+
199
+ ## License
200
+
201
+ This project is licensed under the MIT License - see the LICENSE file for details.
data/bin/tron-wallet ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/tron'
4
+
5
+ def format_number(num)
6
+ num.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse
7
+ end
8
+
9
+ def check_balances(address)
10
+ raise ArgumentError, 'Invalid TRON address.' if address.empty? || !address.start_with?('T')
11
+
12
+ client = Tron::Client.new
13
+
14
+ puts '═' * 60
15
+ puts 'TRON WALLET BALANCE CHECKER'
16
+ puts '═' * 60
17
+ puts "Wallet: #{address}\n\n"
18
+
19
+ puts 'TRX Balance:'
20
+ trx_balance = client.balance_service.get_trx(address)
21
+ puts " #{trx_balance} TRX\n\n"
22
+
23
+ puts 'TRC20 Token Balances:'
24
+ tokens = client.balance_service.get_trc20_tokens(address)
25
+ if tokens.empty?
26
+ puts ' (No token balances found)'
27
+ else
28
+ tokens.each { |t| puts " #{t[:symbol].ljust(10)} #{sprintf("%.#{t[:decimals]}f", t[:balance])}" }
29
+ end
30
+
31
+ puts "\nAccount Resources:"
32
+ res = client.resources_service.get(address)
33
+ puts " Bandwidth: #{format_number(res[:bandwidth]).ljust(15)} / #{format_number(res[:bandwidthLimit])}"
34
+ puts " Energy: #{format_number(res[:energy]).ljust(15)} / #{format_number(res[:energyLimit])}"
35
+ puts '═' * 60
36
+ end
37
+
38
+ wallet_address = ARGV[0]
39
+ if wallet_address.nil? || wallet_address.empty?
40
+ puts 'Error: No wallet address provided.'
41
+ puts 'Usage: ruby bin/tron-wallet <TRON_WALLET_ADDRESS>'
42
+ exit 1
43
+ end
44
+
45
+ begin
46
+ check_balances(wallet_address)
47
+ rescue => e
48
+ puts "Error: #{e.message}"
49
+ exit 1
50
+ end
@@ -0,0 +1,75 @@
1
+ # lib/tron/client.rb
2
+ require_relative 'configuration'
3
+ require_relative 'services/balance'
4
+ require_relative 'services/resources'
5
+ require_relative 'services/price'
6
+
7
+ module Tron
8
+ class Client
9
+ attr_reader :configuration
10
+
11
+ def initialize(options = {})
12
+ @configuration = Configuration.new
13
+
14
+ # Apply options
15
+ options.each do |key, value|
16
+ if @configuration.respond_to?("#{key}=")
17
+ @configuration.send("#{key}=", value)
18
+ end
19
+ end
20
+
21
+ # Load from environment if not set in options
22
+ @configuration.api_key ||= ENV['TRONGRID_API_KEY']
23
+ @configuration.tronscan_api_key ||= ENV['TRONSCAN_API_KEY']
24
+ end
25
+
26
+ def self.configure
27
+ yield configuration if block_given?
28
+ end
29
+
30
+ def self.configuration
31
+ @configuration ||= Configuration.new
32
+ end
33
+
34
+ def balance_service
35
+ @balance_service ||= Services::Balance.new(@configuration)
36
+ end
37
+
38
+ def resources_service
39
+ @resources_service ||= Services::Resources.new(@configuration)
40
+ end
41
+
42
+ def price_service
43
+ @price_service ||= Services::Price.new(@configuration)
44
+ end
45
+
46
+ # Convenience methods that combine multiple services
47
+ def get_wallet_balance(address, strict: false)
48
+ validate_address!(address)
49
+
50
+ {
51
+ address: address,
52
+ trx_balance: balance_service.get_trx(address),
53
+ trc20_tokens: balance_service.get_trc20_tokens(address, strict: strict)
54
+ }
55
+ end
56
+
57
+ def get_full_account_info(address, strict: false)
58
+ validate_address!(address)
59
+
60
+ {
61
+ address: address,
62
+ trx_balance: balance_service.get_trx(address),
63
+ trc20_tokens: balance_service.get_trc20_tokens(address, strict: strict),
64
+ resources: resources_service.get(address)
65
+ }
66
+ end
67
+
68
+ private
69
+
70
+ def validate_address!(address)
71
+ require_relative 'utils/address'
72
+ raise ArgumentError, "Invalid TRON address: #{address}" unless Utils::Address.validate(address)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,37 @@
1
+ # lib/tron/configuration.rb
2
+ module Tron
3
+ class Configuration
4
+ attr_accessor :api_key, :tronscan_api_key, :network, :timeout, :base_url, :tronscan_base_url, :strict_mode
5
+
6
+ def initialize
7
+ @network = :mainnet
8
+ @timeout = 30
9
+ @strict_mode = false
10
+ setup_urls
11
+ end
12
+
13
+ def network=(network)
14
+ @network = network
15
+ setup_urls
16
+ end
17
+
18
+ private
19
+
20
+ def setup_urls
21
+ case @network
22
+ when :mainnet
23
+ @base_url = 'https://api.trongrid.io'
24
+ @tronscan_base_url = 'https://apilist.tronscanapi.com'
25
+ when :shasta
26
+ @base_url = 'https://api.shasta.trongrid.io'
27
+ @tronscan_base_url = 'https://api.shasta.tronscanapi.com'
28
+ when :nile
29
+ @base_url = 'https://nile.trongrid.io'
30
+ @tronscan_base_url = 'https://api.nileex.net'
31
+ else
32
+ @base_url = 'https://api.trongrid.io'
33
+ @tronscan_base_url = 'https://apilist.tronscanapi.com'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,112 @@
1
+ # lib/tron/services/balance.rb
2
+ require_relative '../utils/http'
3
+ require_relative '../utils/address'
4
+
5
+ module Tron
6
+ module Services
7
+ class Balance
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def get_trx(address)
13
+ validate_address!(address)
14
+
15
+ url = "#{@config.base_url}/v1/accounts/#{address}"
16
+ headers = api_headers
17
+
18
+ response = Utils::HTTP.get(url, headers)
19
+
20
+ # Validate response structure
21
+ raise "Unexpected API response format" unless response.is_a?(Hash)
22
+ raise "Missing 'data' field in response" unless response.key?('data')
23
+ raise "Invalid 'data' format in response" unless response['data'].is_a?(Array)
24
+ raise "Empty account data in response" if response['data'].empty?
25
+
26
+ account_data = response['data'].first
27
+ raise "Invalid account data format" unless account_data.is_a?(Hash)
28
+
29
+ # The balance field is only present when > 0; defaults to 0 for new/empty accounts
30
+ raw_balance = account_data.fetch('balance', 0)
31
+ format_balance(raw_balance, 6)
32
+ end
33
+
34
+ def get_trc20_tokens(address, strict: false)
35
+ validate_address!(address)
36
+
37
+ url = "#{@config.tronscan_base_url}/api/account/wallet?address=#{address}&asset_type=1"
38
+ headers = tronscan_headers
39
+
40
+ response = Utils::HTTP.get(url, headers)
41
+
42
+ # Validate response structure
43
+ raise "Unexpected API response format for TRC20 tokens" unless response.is_a?(Hash)
44
+ raise "Missing 'data' field in TRC20 response" unless response.key?('data')
45
+ raise "Invalid 'data' format in TRC20 response" unless response['data'].is_a?(Array)
46
+
47
+ response['data'].select { |token| token['token_type'] == 20 && token['balance'].to_f > 0 }
48
+ .map do |token|
49
+ validate_token_data!(token)
50
+ {
51
+ symbol: token['token_abbr'] || token['token_name'],
52
+ name: token['token_name'],
53
+ balance: token['balance'].to_f,
54
+ decimals: (token['token_decimal'] || 6).to_i,
55
+ address: token['token_id']
56
+ }
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def validate_token_data!(token)
63
+ unless token.is_a?(Hash)
64
+ raise "Invalid token data format: expected hash"
65
+ end
66
+
67
+ # Check required fields exist
68
+ ['token_type', 'balance'].each do |field|
69
+ unless token.key?(field)
70
+ raise "Missing required field '#{field}' in token data: #{token}"
71
+ end
72
+ end
73
+ end
74
+
75
+ def get_all(address, strict: false)
76
+ validate_address!(address)
77
+
78
+ {
79
+ address: address,
80
+ trx_balance: get_trx(address),
81
+ trc20_tokens: get_trc20_tokens(address, strict: strict)
82
+ }
83
+ end
84
+
85
+ private
86
+
87
+ def validate_address!(address)
88
+ raise ArgumentError, "Invalid TRON address: #{address}" unless Utils::Address.validate(address)
89
+ end
90
+
91
+ def format_balance(raw_balance, decimals)
92
+ balance = raw_balance.to_i
93
+ divisor = 10 ** decimals
94
+ whole = balance / divisor
95
+ fraction = balance % divisor
96
+ "#{whole}.#{fraction.to_s.rjust(decimals, '0')}"
97
+ end
98
+
99
+ def api_headers
100
+ headers = { 'accept' => 'application/json' }
101
+ headers['TRON-PRO-API-KEY'] = @config.api_key if @config.api_key
102
+ headers
103
+ end
104
+
105
+ def tronscan_headers
106
+ headers = { 'accept' => 'application/json' }
107
+ headers['TRON-PRO-API-KEY'] = @config.tronscan_api_key if @config.tronscan_api_key
108
+ headers
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,116 @@
1
+ # lib/tron/services/price.rb
2
+ require_relative '../utils/http'
3
+
4
+ module Tron
5
+ module Services
6
+ class Price
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def get_token_price(token = 'trx')
12
+ url = "#{@config.tronscan_base_url}/api/token/price?token=#{token}"
13
+ headers = tronscan_headers
14
+
15
+ response = Utils::HTTP.get(url, headers)
16
+
17
+ # Validate response structure
18
+ unless response.is_a?(Hash)
19
+ raise "Unexpected API response format for token price"
20
+ end
21
+
22
+ response
23
+ rescue => e
24
+ if @config.strict_mode
25
+ raise e
26
+ else
27
+ warn "Warning: Could not fetch price for #{token}: #{e.message}"
28
+ nil
29
+ end
30
+ end
31
+
32
+ def get_all_prices
33
+ url = "#{@config.tronscan_base_url}/api/getAssetWithPriceList"
34
+ headers = tronscan_headers
35
+
36
+ response = Utils::HTTP.get(url, headers)
37
+
38
+ # Validate response structure
39
+ unless response.is_a?(Hash)
40
+ raise "Unexpected API response format for price list"
41
+ end
42
+
43
+ response
44
+ rescue => e
45
+ if @config.strict_mode
46
+ raise e
47
+ else
48
+ warn "Warning: Could not fetch token price list: #{e.message}"
49
+ nil
50
+ end
51
+ end
52
+
53
+ def get_token_price_usd(token)
54
+ price_data = get_token_price(token)
55
+ return nil unless price_data.is_a?(Hash)
56
+
57
+ if price_data['price_in_usd']
58
+ price_data['price_in_usd'].to_f
59
+ elsif price_data['priceInUsd']
60
+ price_data['priceInUsd'].to_f
61
+ else
62
+ nil
63
+ end
64
+ end
65
+
66
+ def get_token_value_usd(balance, token)
67
+ price = get_token_price_usd(token)
68
+ if price
69
+ balance_num = balance.to_f
70
+ balance_num * price
71
+ else
72
+ nil
73
+ end
74
+ end
75
+
76
+ def get_multiple_token_prices(tokens)
77
+ prices = {}
78
+ tokens.each_with_index do |token, index|
79
+ # Add a small delay between requests to avoid rate limiting
80
+ sleep(0.1) if index > 0
81
+ begin
82
+ prices[token] = get_token_price_usd(token)
83
+ rescue => e
84
+ if @config.strict_mode
85
+ raise e
86
+ else
87
+ warn "Warning: Could not fetch price for #{token}: #{e.message}"
88
+ prices[token] = nil
89
+ end
90
+ end
91
+ end
92
+ prices
93
+ end
94
+
95
+ def format_price(price, currency = 'USD')
96
+ if price.nil?
97
+ '(price unavailable)'
98
+ elsif price < 0.0001
99
+ "#{sprintf('%.8f', price)} #{currency}"
100
+ elsif price < 1
101
+ "#{sprintf('%.6f', price)} #{currency}"
102
+ else
103
+ "#{sprintf('%.4f', price)} #{currency}"
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def tronscan_headers
110
+ headers = { 'accept' => 'application/json' }
111
+ headers['TRON-PRO-API-KEY'] = @config.tronscan_api_key if @config.tronscan_api_key
112
+ headers
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,127 @@
1
+ # lib/tron/services/resources.rb
2
+ require_relative '../utils/http'
3
+ require_relative '../utils/address'
4
+
5
+ module Tron
6
+ module Services
7
+ class Resources
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def get(address)
13
+ validate_address!(address)
14
+
15
+ # Try getaccountresource first
16
+ begin
17
+ get_account_resources(address)
18
+ rescue => e
19
+ # Fallback to getaccount if getaccountresource fails
20
+ get_account_resources_fallback(address)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def get_account_resources(address)
27
+ url = "#{@config.base_url}/wallet/getaccountresource"
28
+ headers = api_headers
29
+
30
+ response = Utils::HTTP.post(url, { address: address, visible: true }.to_json, headers)
31
+
32
+ # Validate response structure
33
+ unless response.is_a?(Hash)
34
+ raise "Unexpected API response format for account resources"
35
+ end
36
+
37
+ # Handle error response format
38
+ if response.key?('Error')
39
+ return default_resources
40
+ end
41
+
42
+ # Validate required fields exist
43
+ ['freeNetLimit', 'freeNetUsed', 'TotalNetLimit', 'EnergyLimit', 'EnergyUsed'].each do |field|
44
+ unless response.key?(field)
45
+ puts "Warning: Missing field '#{field}' in account resources response: #{response}" if $DEBUG
46
+ end
47
+ end
48
+
49
+ free_net_limit = response['freeNetLimit']&.to_i || 0
50
+ free_net_used = response['freeNetUsed']&.to_i || 0
51
+ total_net_limit = response['TotalNetLimit']&.to_i || 0
52
+ energy_limit = response['EnergyLimit']&.to_i || 0
53
+ energy_used = response['EnergyUsed']&.to_i || 0
54
+
55
+ available_bandwidth = [0, free_net_limit - free_net_used].max
56
+ available_energy = [0, energy_limit - energy_used].max
57
+ available_total_net = [0, total_net_limit].max
58
+
59
+ {
60
+ bandwidth: available_bandwidth,
61
+ bandwidthLimit: free_net_limit,
62
+ energy: available_energy,
63
+ energyLimit: energy_limit,
64
+ storage: 0,
65
+ storageLimit: 0,
66
+ totalFreeBandwidth: available_total_net,
67
+ totalFreeBandwidthLimit: total_net_limit
68
+ }
69
+ end
70
+
71
+ def get_account_resources_fallback(address)
72
+ url = "#{@config.base_url}/wallet/getaccount"
73
+ headers = api_headers
74
+
75
+ response = Utils::HTTP.post(url, { address: address, visible: true }.to_json, headers)
76
+
77
+ # Validate response structure
78
+ unless response.is_a?(Hash) && response.key?('data')
79
+ raise "Unexpected response format from getaccount endpoint"
80
+ end
81
+
82
+ account_data = response['data']&.first || {}
83
+
84
+ free_net_limit = account_data['freeNetLimit']&.to_i || 0
85
+ free_net_used = account_data['freeNetUsed']&.to_i || 0
86
+ energy_limit = account_data['EnergyLimit']&.to_i || 0
87
+ energy_used = account_data['EnergyUsed']&.to_i || 0
88
+ storage_limit = account_data['StorageLimit']&.to_i || 0
89
+ storage_used = account_data['StorageUsed']&.to_i || 0
90
+
91
+ {
92
+ bandwidth: [0, free_net_limit - free_net_used].max,
93
+ bandwidthLimit: free_net_limit,
94
+ energy: [0, energy_limit - energy_used].max,
95
+ energyLimit: energy_limit,
96
+ storage: [0, storage_limit - storage_used].max,
97
+ storageLimit: storage_limit,
98
+ totalFreeBandwidth: 0,
99
+ totalFreeBandwidthLimit: 0
100
+ }
101
+ end
102
+
103
+ def validate_address!(address)
104
+ raise ArgumentError, "Invalid TRON address: #{address}" unless Utils::Address.validate(address)
105
+ end
106
+
107
+ def api_headers
108
+ headers = { 'accept' => 'application/json', 'Content-Type' => 'application/json' }
109
+ headers['TRON-PRO-API-KEY'] = @config.api_key if @config.api_key
110
+ headers
111
+ end
112
+
113
+ def default_resources
114
+ {
115
+ bandwidth: 0,
116
+ bandwidthLimit: 0,
117
+ energy: 0,
118
+ energyLimit: 0,
119
+ storage: 0,
120
+ storageLimit: 0,
121
+ totalFreeBandwidth: 0,
122
+ totalFreeBandwidthLimit: 0
123
+ }
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,39 @@
1
+ # lib/tron/utils/address.rb
2
+ require 'base58'
3
+
4
+ module Tron
5
+ module Utils
6
+ class Address
7
+ def self.validate(address)
8
+ return false unless address && address.length == 34 && address.start_with?('T')
9
+
10
+ begin
11
+ base58_part = address[1..-1]
12
+ Base58.decode(base58_part)
13
+ true
14
+ rescue
15
+ false
16
+ end
17
+ end
18
+
19
+ def self.to_hex(address)
20
+ return address if address.start_with?('41') && address.length == 42 # Already hex format
21
+
22
+ base58_part = address.start_with?('T') ? address[1..-1] : address
23
+ decoded = Base58.decode(base58_part)
24
+ '41' + decoded.unpack('H*').first
25
+ end
26
+
27
+ def self.to_base58(hex_address)
28
+ return hex_address if hex_address.start_with?('T') && hex_address.length == 34 # Already base58 format
29
+
30
+ hex_without_prefix = hex_address.start_with?('41') ? hex_address[2..-1] : hex_address
31
+ address_bytes = [hex_without_prefix].pack('H*')
32
+
33
+ require 'digest'
34
+ checksum = Digest::SHA256.digest(Digest::SHA256.digest(address_bytes))[0...4]
35
+ Base58.binary_to_base58(address_bytes + checksum)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ # lib/tron/utils/http.rb
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'json'
5
+
6
+ module Tron
7
+ module Utils
8
+ class HTTP
9
+ def self.get(url, headers = {})
10
+ make_request(Net::HTTP::Get, url, nil, headers)
11
+ end
12
+
13
+ def self.post(url, body = nil, headers = {})
14
+ make_request(Net::HTTP::Post, url, body, headers)
15
+ end
16
+
17
+ private
18
+
19
+ def self.make_request(method_class, url, body, headers)
20
+ uri = URI(url)
21
+ http = Net::HTTP.new(uri.host, uri.port)
22
+ http.use_ssl = true if uri.scheme == 'https'
23
+ http.read_timeout = Tron.configuration.timeout
24
+
25
+ request = method_class.new(uri.request_uri, headers)
26
+ request.body = body if body
27
+
28
+ response = http.request(request)
29
+
30
+ case response
31
+ when Net::HTTPSuccess
32
+ json_response = JSON.parse(response.body)
33
+ # Validate that the response is actually a hash/array before returning
34
+ unless json_response.is_a?(Hash) || json_response.is_a?(Array)
35
+ raise "Invalid response format: expected JSON object or array"
36
+ end
37
+ json_response
38
+ else
39
+ raise "API Error: #{response.code} - #{response.body}"
40
+ end
41
+ rescue JSON::ParserError
42
+ raise "Invalid JSON response: #{response.body}"
43
+ rescue => e
44
+ raise "HTTP Error: #{e.message}"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/tron/version.rb
4
+ module Tron
5
+ VERSION = "1.0.0".freeze
6
+ end
data/lib/tron.rb ADDED
@@ -0,0 +1,32 @@
1
+ # lib/tron.rb
2
+ require 'dotenv/load' if File.exist?('.env')
3
+
4
+ require_relative 'tron/version'
5
+ require_relative 'tron/client'
6
+ require_relative 'tron/configuration'
7
+
8
+ module Tron
9
+ class << self
10
+ def client
11
+ @client ||= Client.new
12
+ end
13
+
14
+ def configure(&block)
15
+ @client = nil # Reset client when configuration changes
16
+ Client.configure(&block)
17
+ end
18
+
19
+ # Delegate common methods to the default client
20
+ def method_missing(method, *args, &block)
21
+ if client.respond_to?(method)
22
+ client.send(method, *args, &block)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def respond_to_missing?(method, include_private = false)
29
+ client.respond_to?(method) || super
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tron.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Your Name
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-10-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base58
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.0'
83
+ description: A Ruby gem for interacting with TRON blockchain APIs, including balance
84
+ checking, resource information, and token prices.
85
+ email:
86
+ - your.email@example.com
87
+ executables:
88
+ - tron-wallet
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - LICENSE
93
+ - README.md
94
+ - bin/tron-wallet
95
+ - lib/tron.rb
96
+ - lib/tron/client.rb
97
+ - lib/tron/configuration.rb
98
+ - lib/tron/services/balance.rb
99
+ - lib/tron/services/price.rb
100
+ - lib/tron/services/resources.rb
101
+ - lib/tron/utils/address.rb
102
+ - lib/tron/utils/http.rb
103
+ - lib/tron/version.rb
104
+ homepage: https://github.com/yourusername/tron
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '2.5'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.1.6
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Ruby wrapper for TRON blockchain APIs
127
+ test_files: []