tron.rb 1.1.2 → 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 +4 -4
- data/README.md +94 -308
- data/lib/tron/abi/constant.rb +18 -0
- data/lib/tron/abi/decoder.rb +174 -0
- data/lib/tron/abi/encoder.rb +293 -0
- data/lib/tron/abi/event.rb +133 -0
- data/lib/tron/abi/function.rb +135 -0
- data/lib/tron/abi/type.rb +261 -0
- data/lib/tron/abi/util.rb +100 -0
- data/lib/tron/abi.rb +153 -0
- data/lib/tron/client.rb +64 -1
- data/lib/tron/configuration.rb +39 -2
- data/lib/tron/contract.rb +157 -0
- data/lib/tron/key.rb +271 -0
- data/lib/tron/protobuf/transaction_raw_serializer.rb +295 -0
- data/lib/tron/protobuf.rb +18 -0
- data/lib/tron/services/balance.rb +43 -2
- data/lib/tron/services/contract.rb +232 -0
- data/lib/tron/services/price.rb +40 -0
- data/lib/tron/services/resources.rb +27 -0
- data/lib/tron/services/transaction.rb +104 -0
- data/lib/tron/signature.rb +21 -0
- data/lib/tron/utils/abi.rb +321 -0
- data/lib/tron/utils/address.rb +63 -10
- data/lib/tron/utils/cache.rb +18 -0
- data/lib/tron/utils/crypto.rb +67 -0
- data/lib/tron/utils/http.rb +49 -4
- data/lib/tron/utils/rate_limiter.rb +16 -0
- data/lib/tron/version.rb +1 -1
- data/lib/tron.rb +30 -1
- metadata +71 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb44ecaf6775b445b5cda375645bdc6f7bfc0cc05900d22fa12a5fb6978d2a6a
|
|
4
|
+
data.tar.gz: 36e1035e31e0eb67e06c12302f4e5df652f8935112e3d4c2dcf2be7b2f5add83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
##
|
|
21
|
+
## Documentation
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
Full documentation is available through YARD. To generate the documentation locally:
|
|
20
24
|
|
|
21
|
-
#### Using Environment Variables:
|
|
22
25
|
```bash
|
|
23
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
client = Tron::Client.new(cache: { enabled: false })
|
|
76
|
-
```
|
|
29
|
+
Then open `doc/index.html` in your browser.
|
|
77
30
|
|
|
78
|
-
|
|
31
|
+
## Key Features
|
|
79
32
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
#
|
|
56
|
+
# Initialize client
|
|
107
57
|
client = Tron::Client.new
|
|
108
58
|
|
|
109
|
-
#
|
|
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
|
-
##
|
|
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
|
-
|
|
65
|
+
The client can be configured using environment variables or programmatically:
|
|
192
66
|
|
|
193
67
|
```ruby
|
|
194
68
|
Tron.configure do |config|
|
|
195
|
-
|
|
196
|
-
config.
|
|
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
|
-
|
|
75
|
+
## Local Transaction Signing
|
|
207
76
|
|
|
208
|
-
|
|
77
|
+
**NEW in v2.0:** Secure local transaction signing with secp256k1!
|
|
209
78
|
|
|
210
|
-
|
|
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
|
-
###
|
|
219
|
-
|
|
220
|
-
Monitor cache performance to optimize your configuration:
|
|
81
|
+
### Quick Start
|
|
221
82
|
|
|
222
83
|
```ruby
|
|
223
|
-
|
|
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
|
-
#
|
|
256
|
-
|
|
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
|
-
#
|
|
259
|
-
|
|
91
|
+
# Get your TRON address
|
|
92
|
+
address = key.address
|
|
93
|
+
# => "TYxyz123..."
|
|
260
94
|
```
|
|
261
95
|
|
|
262
|
-
###
|
|
263
|
-
|
|
264
|
-
#### Disable Caching for Specific Requests
|
|
96
|
+
### Signing Transactions
|
|
265
97
|
|
|
266
98
|
```ruby
|
|
267
|
-
#
|
|
268
|
-
|
|
269
|
-
```
|
|
99
|
+
# Initialize client
|
|
100
|
+
client = Tron::Client.new(network: :mainnet)
|
|
270
101
|
|
|
271
|
-
|
|
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
|
-
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
129
|
+
The library provides a convenient service:
|
|
310
130
|
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
364
|
-
- USDC: `TEkxiTehnzSmAaVPYYJNTY7v1KHVqCvRdx`
|
|
365
|
-
- USDD: `TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn`
|
|
366
|
-
- TUSD: `TUpMhErZL2fhh4sVNULzmL7sbb8NkK57eX`
|
|
367
|
-
- WBTC: `TXpw8XeWYeTUd4quDskoUqeQPowRh4jY65`
|
|
162
|
+
### Testing on Shasta Testnet
|
|
368
163
|
|
|
369
|
-
|
|
164
|
+
Before using real funds, test on Shasta testnet:
|
|
370
165
|
|
|
371
|
-
|
|
372
|
-
|
|
166
|
+
```ruby
|
|
167
|
+
# Use Shasta testnet
|
|
168
|
+
client = Tron::Client.new(network: :shasta)
|
|
373
169
|
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
380
|
-
```bash
|
|
381
|
-
bundle install
|
|
382
|
-
bundle exec rake test
|
|
173
|
+
# Your code here...
|
|
383
174
|
```
|
|
384
175
|
|
|
385
|
-
|
|
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
|