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 +7 -0
- data/LICENSE +21 -0
- data/README.md +201 -0
- data/bin/tron-wallet +50 -0
- data/lib/tron/client.rb +75 -0
- data/lib/tron/configuration.rb +37 -0
- data/lib/tron/services/balance.rb +112 -0
- data/lib/tron/services/price.rb +116 -0
- data/lib/tron/services/resources.rb +127 -0
- data/lib/tron/utils/address.rb +39 -0
- data/lib/tron/utils/http.rb +48 -0
- data/lib/tron/version.rb +6 -0
- data/lib/tron.rb +32 -0
- metadata +127 -0
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
|
data/lib/tron/client.rb
ADDED
@@ -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
|
data/lib/tron/version.rb
ADDED
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: []
|