tron.rb 1.0.9 → 1.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 530fde2195467fe3316fced5499bb3d4a82823b47ee0032fca7c676f89cf3b6c
4
- data.tar.gz: 518fa7e74e09d85694f00aee805e10debd469798969d7eff715bc73bfb0be7ef
3
+ metadata.gz: cb44ecaf6775b445b5cda375645bdc6f7bfc0cc05900d22fa12a5fb6978d2a6a
4
+ data.tar.gz: 36e1035e31e0eb67e06c12302f4e5df652f8935112e3d4c2dcf2be7b2f5add83
5
5
  SHA512:
6
- metadata.gz: 62e0177fdab3c7046b2b3db15745ac01b4ae7db31acae4b3f0b1cf570dde96d3c4e56f6e80185ad323b3a2de6378b4b9f9fbc6536d9590b705c361d2ed95f01f
7
- data.tar.gz: 14caf1ed3a549c8423db2562416ec0cc894b4fd8398acbc69d2c646f008c6cff7240c86ed2b811fdfb1ec3e204516d49c1cefa324de0f43a4d70d1c9c7cae1c7
6
+ metadata.gz: 63d164e6bc937089543273590658b616395082aa8bc8e0b4054bbc2e2e63cbdd34d44d02a42a3f8c79bec3cf58ea333aa0aa4271b8d9a20f489075631d29727b
7
+ data.tar.gz: 218564b3026bb5a33fdf44f328bc609b5f3627f09ff644d2b5d9372e272477f02238c44562892420ac5194520e0b9784cf3ea668617bbf017e22720504699a8c
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # TRON Ruby Client
2
2
 
3
- A Ruby gem for interacting with the TRON blockchain to check wallet balances and related information.
3
+ A Ruby gem for interacting with the TRON blockchain with comprehensive features including key management, contract interaction, and enhanced security.
4
+
5
+ ## Requirements
6
+
7
+ - Ruby >= 3.0 (required for keccak cryptographic library)
4
8
 
5
9
  ## Installation
6
10
 
@@ -14,79 +18,26 @@ Or add to your Gemfile:
14
18
  gem 'tron.rb'
15
19
  ```
16
20
 
17
- ## Configuration
21
+ ## Documentation
18
22
 
19
- You can configure the client using environment variables or programmatically:
23
+ Full documentation is available through YARD. To generate the documentation locally:
20
24
 
21
- #### Using Environment Variables:
22
25
  ```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
-
37
- # Cache configuration (optional)
38
- config.cache_enabled = true # Enable/disable caching (default: true)
39
- config.cache_ttl = 300 # Cache TTL in seconds (default: 300 = 5 minutes)
40
- config.cache_max_stale = 600 # Max stale time in seconds (default: 600 = 10 minutes)
41
- end
42
- ```
43
-
44
- ### Cache Configuration
45
-
46
- The gem includes intelligent caching to prevent rate limit errors and improve performance.
47
-
48
- #### Default Configuration (Recommended)
49
-
50
- ```ruby
51
- client = Tron::Client.new(
52
- api_key: ENV['TRONGRID_API_KEY'],
53
- tronscan_api_key: ENV['TRONSCAN_API_KEY']
54
- )
55
- # Cache enabled by default with 5-minute TTL
56
- ```
57
-
58
- #### Custom Cache Configuration
59
-
60
- ```ruby
61
- client = Tron::Client.new(
62
- api_key: ENV['TRONGRID_API_KEY'],
63
- tronscan_api_key: ENV['TRONSCAN_API_KEY'],
64
- cache: {
65
- enabled: true,
66
- ttl: 60, # Cache for 1 minute
67
- max_stale: 600 # Serve stale data up to 10 minutes if API fails
68
- }
69
- )
26
+ yard doc
70
27
  ```
71
28
 
72
- #### Disable Cache
73
-
74
- ```ruby
75
- client = Tron::Client.new(cache: { enabled: false })
76
- ```
29
+ Then open `doc/index.html` in your browser.
77
30
 
78
- #### Monitor Cache Performance
31
+ ## Key Features
79
32
 
80
- ```ruby
81
- stats = client.cache_stats
82
- puts "Price cache hit rate: #{stats[:price][:hit_rate]}%"
83
- puts "Balance cache hit rate: #{stats[:balance][:hit_rate]}%"
84
-
85
- # Clear cache manually
86
- client.clear_cache
87
- ```
88
-
89
- **Performance:** Caching provides 2,000x+ faster response times for repeated requests and eliminates 429 rate limit errors.
33
+ - **Local Key Management**: Generate and manage private/public key pairs locally
34
+ - **Secure Transaction Signing**: Sign transactions locally without sending private keys to APIs
35
+ - **Comprehensive ABI Support**: Full Solidity ABI support for complex contract interactions
36
+ - **TRON-Specific Address Handling**: Correct TRON address format using Base58 with 'T' prefix
37
+ - **Protocol Buffer Serialization**: Proper TRON transaction serialization
38
+ - **Enhanced Security**: Private keys never leave the machine during signing
39
+ - **Wallet Operations**: Balance checking, resource info, token prices, and portfolio tracking
40
+ - **Smart Contract Interaction**: Call and trigger smart contracts
90
41
 
91
42
  ## Usage
92
43
 
@@ -99,295 +50,130 @@ ruby bin/tron-wallet TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb
99
50
 
100
51
  ### As a Library
101
52
 
102
- #### Initialize Client:
103
53
  ```ruby
104
54
  require 'tron'
105
55
 
106
- # Use default configuration (loads from ENV)
56
+ # Initialize client
107
57
  client = Tron::Client.new
108
58
 
109
- # Or with custom configuration
110
- client = Tron::Client.new(
111
- api_key: 'your_trongrid_api',
112
- tronscan_api_key: 'your_tronscan_api',
113
- network: :mainnet,
114
- timeout: 30
115
- )
116
- ```
117
-
118
- #### Get TRX Balance:
119
- ```ruby
120
- trx_balance = client.balance_service.get_trx('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
121
- ```
122
-
123
- #### Get TRC20 Token Balances:
124
- ```ruby
125
- tokens = client.balance_service.get_trc20_tokens('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
126
- ```
127
-
128
- #### Get Account Resources:
129
- ```ruby
130
- resources = client.resources_service.get('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
131
- ```
132
-
133
- #### Get Token Prices:
134
- ```ruby
135
- price = client.price_service.get_token_price('trx')
136
- usd_price = client.price_service.get_token_price_usd('trx')
137
- ```
138
-
139
- #### Get Complete Wallet Information:
140
- ```ruby
59
+ # Get wallet information
141
60
  wallet_info = client.get_wallet_balance('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
142
- full_info = client.get_full_account_info('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
143
- ```
144
-
145
- #### Get Wallet Portfolio with USD Values:
146
- ```ruby
147
- # Get wallet portfolio with balances, USD prices, and total value
148
- portfolio = client.get_wallet_portfolio('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb')
149
-
150
- # Include tokens with zero balance
151
- portfolio = client.get_wallet_portfolio('TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb', include_zero_balances: true)
152
-
153
- puts "Total Portfolio Value: $#{portfolio[:total_value_usd]}"
154
- portfolio[:tokens].each do |token|
155
- puts "#{token[:symbol]}: #{token[:token_balance]} ($#{token[:usd_value]})"
156
- end
157
61
  ```
158
62
 
159
- ## Features
160
-
161
- - ✓ TRX balance
162
- - ✓ TRC20 token balances (USDT, USDC, USDD, TUSD, WBTC)
163
- - ✓ Account resources (bandwidth & energy)
164
- - ✓ Token prices
165
- - ✓ Portfolio tracking
166
- - ✓ Multi-network support (mainnet, shasta, nile)
167
- - ✓ Intelligent caching with TTL and stale-while-revalidate
168
- - ✓ Cache statistics and monitoring
169
- - ✓ Clean, simple output
170
- - ✓ Proper decimal formatting
171
- - ✓ Environment variable support
172
- - ✓ Rate limit management with API keys
173
-
174
- ## Caching
175
-
176
- The gem includes an intelligent caching system to reduce API calls and improve performance.
177
-
178
- ### How Caching Works
179
-
180
- The cache uses a **time-based expiration strategy** with **stale-while-revalidate** behavior:
181
-
182
- 1. **Fresh Cache (age < TTL)**: Returns cached value immediately
183
- 2. **Stale Cache (age > TTL but < max_stale)**: Attempts to refresh, falls back to stale value on error
184
- 3. **Expired Cache (age > max_stale)**: Forces refresh, raises error if refresh fails
185
-
186
- This approach ensures:
187
- - Fast responses from fresh cache
188
- - High availability with stale fallbacks
189
- - Automatic background refresh for popular endpoints
63
+ ## Configuration
190
64
 
191
- ### Cache Configuration
65
+ The client can be configured using environment variables or programmatically:
192
66
 
193
67
  ```ruby
194
68
  Tron.configure do |config|
195
- # Enable or disable caching globally (default: true)
196
- config.cache_enabled = true
197
-
198
- # Set default TTL (time-to-live) in seconds (default: 300 = 5 minutes)
199
- config.cache_ttl = 300
200
-
201
- # Set max stale time in seconds (default: 600 = 10 minutes)
202
- config.cache_max_stale = 600
69
+ config.api_key = 'your_trongrid_api_key'
70
+ config.network = :mainnet # or :shasta or :nile
71
+ config.timeout = 30
203
72
  end
204
73
  ```
205
74
 
206
- ### Endpoint-Specific TTL Values
75
+ ## Local Transaction Signing
207
76
 
208
- Different endpoints have optimized TTL values based on data volatility:
77
+ **NEW in v2.0:** Secure local transaction signing with secp256k1!
209
78
 
210
- | Endpoint Type | TTL | Max Stale | Use Case |
211
- |---------------|-----|-----------|----------|
212
- | **balance** | 5 min | 10 min | Wallet balances, moderate volatility |
213
- | **price** | 1 min | 2 min | Token prices, high volatility |
214
- | **token_info** | 15 min | 30 min | Token metadata, low volatility |
215
- | **resources** | 5 min | 10 min | Account resources, moderate volatility |
216
- | **default** | 5 min | 10 min | Unclassified endpoints |
79
+ Your private keys **NEVER** leave your machine when using local signing.
217
80
 
218
- ### Cache Statistics
219
-
220
- Monitor cache performance to optimize your configuration:
81
+ ### Quick Start
221
82
 
222
83
  ```ruby
223
- # Get global cache statistics
224
- stats = Tron::Cache.global_stats
225
- puts "Cache Hit Rate: #{stats[:hit_rate_percentage]}%"
226
- puts "Total Hits: #{stats[:total_hits]}"
227
- puts "Total Misses: #{stats[:total_misses]}"
228
- puts "Cache Size: #{stats[:cache_size]} entries"
229
-
230
- # Get statistics for a specific cache entry
231
- key_stats = Tron::Cache.stats("specific_key")
232
- if key_stats
233
- puts "Entry Hits: #{key_stats[:hits]}"
234
- puts "Entry Misses: #{key_stats[:misses]}"
235
- puts "Cached At: #{key_stats[:cached_at]}"
236
- puts "Expires At: #{key_stats[:expires_at]}"
237
- puts "Expired: #{key_stats[:expired]}"
238
- end
239
- ```
240
-
241
- ### Cache Management
242
-
243
- ```ruby
244
- # Clear entire cache
245
- Tron::Cache.clear
246
-
247
- # Delete specific cache entry
248
- Tron::Cache.delete("cache_key")
249
-
250
- # Check if key exists in cache
251
- if Tron::Cache.exists?("cache_key")
252
- puts "Key is cached"
253
- end
84
+ require 'tron'
254
85
 
255
- # Get cache size
256
- puts "Cache has #{Tron::Cache.size} entries"
86
+ # Generate a new key or import existing one
87
+ key = Tron::Key.new # Generate new key
88
+ # OR
89
+ key = Tron::Key.new(priv: "your_hex_key") # Import existing key
257
90
 
258
- # Reset statistics (useful for testing)
259
- Tron::Cache.reset_stats
91
+ # Get your TRON address
92
+ address = key.address
93
+ # => "TYxyz123..."
260
94
  ```
261
95
 
262
- ### Advanced Cache Usage
263
-
264
- #### Disable Caching for Specific Requests
96
+ ### Signing Transactions
265
97
 
266
98
  ```ruby
267
- # Disable cache for a specific HTTP request
268
- response = Tron::Utils::HTTP.get(url, headers, { enabled: false })
269
- ```
99
+ # Initialize client
100
+ client = Tron::Client.new(network: :mainnet)
270
101
 
271
- #### Custom TTL for Specific Requests
102
+ # Step 1: Create transaction via TronGrid API
103
+ from_hex = Tron::Utils::Address.to_hex(from_address)
104
+ to_hex = Tron::Utils::Address.to_hex(to_address)
272
105
 
273
- ```ruby
274
- # Use custom TTL values for a specific request
275
- response = Tron::Utils::HTTP.get(url, headers, {
276
- ttl: 60, # 1 minute
277
- max_stale: 120 # 2 minutes
278
- })
279
- ```
106
+ transaction = Tron::Utils::HTTP.post(
107
+ "#{client.configuration.base_url}/wallet/createtransaction",
108
+ {
109
+ owner_address: from_hex,
110
+ to_address: to_hex,
111
+ amount: 1_000_000 # 1 TRX in SUN
112
+ }
113
+ )
280
114
 
281
- #### Use Endpoint-Specific TTL
115
+ # Step 2: Sign locally (SECURE - private key stays on your machine!)
116
+ tx_hash = Tron::Utils::Crypto.hex_to_bin(transaction['txID'])
117
+ signature = key.sign(tx_hash)
118
+ transaction['signature'] = [signature]
282
119
 
283
- ```ruby
284
- # Use predefined TTL for specific endpoint type
285
- response = Tron::Utils::HTTP.get(url, headers, {
286
- endpoint_type: :price # Uses 1 min TTL, 2 min max_stale
287
- })
120
+ # Step 3: Broadcast
121
+ result = Tron::Utils::HTTP.post(
122
+ "#{client.configuration.base_url}/wallet/broadcasttransaction",
123
+ transaction
124
+ )
288
125
  ```
289
126
 
290
- ### Cache Best Practices
291
-
292
- 1. **Enable caching in production** - Reduces API calls and improves response times
293
- 2. **Monitor hit rates** - Aim for >70% hit rate for frequently accessed data
294
- 3. **Adjust TTL based on data volatility** - Shorter TTL for prices, longer for token metadata
295
- 4. **Use stale fallbacks** - Ensures high availability during API issues
296
- 5. **Clear cache after mutations** - If you modify data, clear related cache entries
297
-
298
- ## Services Architecture
299
-
300
- The gem is organized into modular services:
301
-
302
- - `Tron::Services::Balance` - TRX and TRC20 token balances
303
- - `Tron::Services::Resources` - Account resources (bandwidth/energy)
304
- - `Tron::Services::Price` - Token price information
305
- - `Tron::Utils::HTTP` - HTTP client with error handling and caching
306
- - `Tron::Utils::Address` - TRON address validation and conversion
307
- - `Tron::Cache` - Thread-safe caching with TTL and statistics
127
+ ### Using Transaction Service
308
128
 
309
- ## API Reference
129
+ The library provides a convenient service:
310
130
 
311
- ### Balance Service
312
- - `get_trx(address)` - Get TRX balance
313
- - `get_trc20_tokens(address)` - Get all TRC20 token balances
314
- - `get_all(address)` - Get all balance information
315
-
316
- ### Resources Service
317
- - `get(address)` - Get account resources (bandwidth, energy)
318
-
319
- ### Price Service
320
- - `get_token_price(token)` - Get price information for a token
321
- - `get_all_prices()` - Get prices for all tokens
322
- - `get_token_price_usd(token)` - Get price in USD
323
- - `get_token_value_usd(balance, token)` - Calculate dollar value
324
- - `get_multiple_token_prices(tokens)` - Get multiple token prices
325
- - `format_price(price, currency)` - Format price with currency
326
-
327
- ## Output Example
328
-
329
- ```
330
- ════════════════════════════════════════════════════════════
331
- TRON WALLET BALANCE CHECKER
332
- ════════════════════════════════════════════════════════════
333
- Wallet: TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb
334
-
335
- TRX Balance:
336
- 1234.567890 TRX
337
-
338
- TRC20 Token Balances:
339
- USDT 100.000000
340
- USDC 50.500000
131
+ ```ruby
132
+ client = Tron::Client.new(network: :mainnet)
341
133
 
342
- Account Resources:
343
- Bandwidth: 1,500 / 5,000
344
- Energy: 10,000 / 50,000
134
+ # Create transaction first (via API)
135
+ transaction = create_your_transaction(...)
345
136
 
346
- ════════════════════════════════════════════════════════════
137
+ # Sign and broadcast in one call
138
+ result = client.transaction_service.sign_and_broadcast(
139
+ transaction,
140
+ your_private_key,
141
+ local_signing: true # DEFAULT - keeps private key secure!
142
+ )
347
143
  ```
348
144
 
349
- ## Environment Variables
350
-
351
- - `TRONGRID_API_KEY` - TronGrid API key (optional, increases rate limits)
352
- - `TRONSCAN_API_KEY` - Tronscan API key (optional, increases rate limits)
145
+ ### Security Features
353
146
 
354
- ## Getting an API Key
147
+ **Local Signing** - Private keys never transmitted
148
+ ✅ **secp256k1** - Industry-standard elliptic curve cryptography
149
+ ✅ **SHA256** - Proper TRON transaction hashing
150
+ ✅ **Base58** - Correct TRON address format with checksum
151
+ ✅ **Testnet Support** - Test on Shasta before using mainnet
355
152
 
356
- 1. Visit [TronGrid](https://www.trongrid.io/)
357
- 2. Sign up for a free account
358
- 3. Get your API key from the dashboard
359
- 4. Use it in your `.env` file or as an environment variable
153
+ ### Complete Example
360
154
 
361
- ## Popular Token Addresses
155
+ See `examples/local_signing_example.rb` for a comprehensive guide including:
156
+ - Key generation and import
157
+ - Transaction creation
158
+ - Local signing
159
+ - Broadcasting
160
+ - Security best practices
362
161
 
363
- - USDT: `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`
364
- - USDC: `TEkxiTehnzSmAaVPYYJNTY7v1KHVqCvRdx`
365
- - USDD: `TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn`
366
- - TUSD: `TUpMhErZL2fhh4sVNULzmL7sbb8NkK57eX`
367
- - WBTC: `TXpw8XeWYeTUd4quDskoUqeQPowRh4jY65`
162
+ ### Testing on Shasta Testnet
368
163
 
369
- ## Contributing
164
+ Before using real funds, test on Shasta testnet:
370
165
 
371
- We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This enables automated release notes and semantic versioning.
372
- We use Minitest for testing.
166
+ ```ruby
167
+ # Use Shasta testnet
168
+ client = Tron::Client.new(network: :shasta)
373
169
 
374
- ### Commit Message Format
375
- - `fix: ...` for bug fixes (triggers PATCH release)
376
- - `feat: ...` for new features (triggers MINOR release)
377
- - `feat!: ...` or `BREAKING CHANGE: ...` for breaking changes (triggers MAJOR release)
170
+ # Get free test TRX from faucet
171
+ # Visit: https://www.trongrid.io/faucet
378
172
 
379
- ### Running Tests
380
- ```bash
381
- bundle install
382
- bundle exec rake test
173
+ # Your code here...
383
174
  ```
384
175
 
385
- ## Automated Releases
386
-
387
- This project uses GitHub Actions to automate releases:
388
- 1. Commits following Conventional Commits format trigger release PRs
389
- 2. Release PRs are automatically created and must be manually reviewed and merged
390
- 3. Once merged to master, the gem is automatically built and published to RubyGems
176
+ **Validated:** Local signing has been tested and confirmed working on TRON Shasta testnet with real transactions!
391
177
 
392
178
  ## License
393
179
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tron
4
+ module Abi
5
+ # Provides constants for ABI encoding/decoding
6
+ module Constant
7
+ extend self
8
+
9
+ # Byte zero constant
10
+ # @return [String] binary string containing zero byte
11
+ BYTE_ZERO = "\x00".b
12
+
13
+ # Byte one constant
14
+ # @return [String] binary string containing one byte
15
+ BYTE_ONE = "\x01".b
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'util'
3
+ require_relative 'constant'
4
+
5
+ module Tron
6
+ module Abi
7
+ # Provides a utility module to assist decoding ABIs.
8
+ module Decoder
9
+ extend self
10
+
11
+ # Decodes a specific value, either static or dynamic.
12
+ #
13
+ # @param type [Tron::Abi::Type] type to be decoded.
14
+ # @param arg [String] encoded type data string.
15
+ # @return [String] the decoded data for the type.
16
+ # @raise [DecodingError] if decoding fails for type.
17
+ def type(type, arg)
18
+ if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
19
+ # Case: decoding a string/bytes
20
+ if type.dimensions.empty?
21
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
22
+ data = arg[32..-1]
23
+ raise DecodingError, "Wrong data size for string/bytes object" unless data.bytesize == Util.ceil32(l)
24
+
25
+ # decoded strings and bytes
26
+ data[0, l]
27
+ # Case: decoding array of string/bytes
28
+ else
29
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
30
+ raise DecodingError, "Wrong data size for dynamic array" unless arg.bytesize >= 32 + 32 * l
31
+
32
+ # Decode each element of the array
33
+ (1..l).map do |i|
34
+ pointer = Util.deserialize_big_endian_to_int arg[i * 32, 32] # Pointer to the size of the array's element
35
+ raise DecodingError, "Offset out of bounds" if pointer < 32 * l || pointer > arg.bytesize - 64
36
+ data_l = Util.deserialize_big_endian_to_int arg[32 + pointer, 32] # length of the element
37
+ raise DecodingError, "Offset out of bounds" if pointer + 32 + Util.ceil32(data_l) > arg.bytesize
38
+ type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32])
39
+ end
40
+ end
41
+ elsif type.base_type == "tuple" && type.dimensions.empty?
42
+ offset = 0
43
+ result = []
44
+ raise DecodingError, "Cannot decode tuples without known components" if type.components.nil?
45
+ type.components.each_with_index do |c, i|
46
+ if c.dynamic?
47
+ pointer = Util.deserialize_big_endian_to_int arg[offset, 32]
48
+ next_offset = if i + 1 < type.components.size
49
+ Util.deserialize_big_endian_to_int arg[offset + 32, 32]
50
+ else
51
+ arg.bytesize
52
+ end
53
+ raise DecodingError, "Offset out of bounds" if pointer > arg.bytesize || next_offset > arg.bytesize || next_offset < pointer
54
+ result << type(c, arg[pointer, next_offset - pointer])
55
+ offset += 32
56
+ else
57
+ size = c.size
58
+ raise DecodingError, "Offset out of bounds" if offset + size > arg.bytesize
59
+ result << type(c, arg[offset, size])
60
+ offset += size
61
+ end
62
+ end
63
+ result
64
+ elsif type.dynamic?
65
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
66
+ nested_sub = type.nested_sub
67
+
68
+ if nested_sub.dynamic?
69
+ raise DecodingError, "Wrong data size for dynamic array" unless arg.bytesize >= 32 + 32 * l
70
+ offsets = (0...l).map do |i|
71
+ off = Util.deserialize_big_endian_to_int arg[32 + 32 * i, 32]
72
+ raise DecodingError, "Offset out of bounds" if off < 32 * l || off > arg.bytesize - 64
73
+ off
74
+ end
75
+ offsets.map { |off| type(nested_sub, arg[32 + off..]) }
76
+ else
77
+ raise DecodingError, "Wrong data size for dynamic array" unless arg.bytesize >= 32 + nested_sub.size * l
78
+ # decoded dynamic-sized arrays with static sub-types
79
+ (0...l).map { |i| type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
80
+ end
81
+ elsif !type.dimensions.empty?
82
+ l = type.dimensions.first
83
+ nested_sub = type.nested_sub
84
+
85
+ if nested_sub.dynamic?
86
+ raise DecodingError, "Wrong data size for static array" unless arg.bytesize >= 32 * l
87
+ offsets = (0...l).map do |i|
88
+ off = Util.deserialize_big_endian_to_int arg[32 * i, 32]
89
+ raise DecodingError, "Offset out of bounds" if off < 32 * l || off > arg.bytesize - 32
90
+ off
91
+ end
92
+ offsets.each_with_index.map do |off, i|
93
+ size = (i + 1 < offsets.length ? offsets[i + 1] : arg.bytesize) - off
94
+ type(nested_sub, arg[off, size])
95
+ end
96
+ else
97
+ # decoded static-size arrays with static sub-types
98
+ (0...l).map { |i| type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
99
+ end
100
+ else
101
+
102
+ # decoded primitive types
103
+ primitive_type type, arg
104
+ end
105
+ end
106
+
107
+ # Decodes primitive types.
108
+ #
109
+ # @param type [Tron::Abi::Type] type to be decoded.
110
+ # @param data [String] encoded primitive type data string.
111
+ # @return [String] the decoded data for the type.
112
+ # @raise [DecodingError] if decoding fails for type.
113
+ def primitive_type(type, data)
114
+ case type.base_type
115
+ when "address"
116
+ # Handle TRON-specific address decoding
117
+ # Addresses are 32 bytes (256 bits) with the last 20 bytes being the actual address
118
+ # The TRON prefix is 0x41, so the address part without prefix should be 40 hex chars
119
+ addr_bytes = data[-20..-1] # Get last 20 bytes
120
+ addr_hex = Util.bin_to_hex(addr_bytes)
121
+
122
+ require_relative '../utils/address'
123
+ # Convert 40 hex chars (20 bytes) to TRON address
124
+ Utils::Address.to_base58(Tron::Key::ADDRESS_PREFIX + addr_hex)
125
+ when "string", "bytes"
126
+ if type.sub_type.empty?
127
+ size = Util.deserialize_big_endian_to_int data[0, 32]
128
+
129
+ # decoded dynamic-sized array
130
+ decoded = data[32..-1][0, size]
131
+ decoded.force_encoding(Encoding::UTF_8)
132
+ decoded
133
+ else
134
+
135
+ # decoded static-sized array
136
+ data[0, type.sub_type.to_i]
137
+ end
138
+ when "hash"
139
+
140
+ # decoded hash
141
+ data[(32 - type.sub_type.to_i), type.sub_type.to_i]
142
+ when "uint"
143
+
144
+ # decoded unsigned integer
145
+ Util.deserialize_big_endian_to_int data
146
+ when "int"
147
+ u = Util.deserialize_big_endian_to_int data
148
+ i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** 256) : u
149
+
150
+ # decoded integer
151
+ i
152
+ when "ureal", "ufixed"
153
+ high, low = type.sub_type.split("x").map(&:to_i)
154
+
155
+ # decoded unsigned fixed point numeric
156
+ Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low
157
+ when "real", "fixed"
158
+ high, low = type.sub_type.split("x").map(&:to_i)
159
+ u = Util.deserialize_big_endian_to_int data
160
+ i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u
161
+
162
+ # decoded fixed point numeric
163
+ i * 1.0 / 2 ** low
164
+ when "bool"
165
+
166
+ # decoded boolean
167
+ data[-1] == ::Tron::Abi::Constant::BYTE_ONE
168
+ else
169
+ raise DecodingError, "Unknown primitive type: #{type.base_type}"
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end