tron.rb 1.0.4 → 1.0.6
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 +180 -1
- data/lib/tron/cache.rb +186 -0
- data/lib/tron/client.rb +16 -0
- data/lib/tron/configuration.rb +17 -1
- data/lib/tron/services/balance.rb +69 -11
- data/lib/tron/services/price.rb +67 -5
- data/lib/tron/services/resources.rb +1 -1
- data/lib/tron/utils/cache.rb +61 -0
- data/lib/tron/utils/http.rb +99 -3
- data/lib/tron/utils/rate_limiter.rb +60 -0
- data/lib/tron/version.rb +1 -1
- data/lib/tron.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e552bde0f9721d76aee5ee554e0f565dcda21275d977e675085c110e4b7cba9
|
|
4
|
+
data.tar.gz: 74b56a49ceca14c4e15efd5fd278e6b6bc0cbb579a68df9c48f219a7531b4979
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: abd8889661b18e91a9283b4f8f533794c2bdeb06c1f470c29702a2e174d0d8eae4e154d79b37d1864710e14214555cfe52655ff83199a52baa94528c2b73b8d2
|
|
7
|
+
data.tar.gz: 369051da2a2179f2aafc7e61f74b3c5b259b4301741ba794afa3bc3ff5ea83986687214bf70105a2c0d413613a467f954116814d818c11ca30e2154f7cc6b518
|
data/README.md
CHANGED
|
@@ -33,9 +33,61 @@ Tron.configure do |config|
|
|
|
33
33
|
config.tronscan_api_key = 'your_tronscan_api_key'
|
|
34
34
|
config.network = :mainnet # or :shasta or :nile
|
|
35
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)
|
|
36
41
|
end
|
|
37
42
|
```
|
|
38
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
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Disable Cache
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
client = Tron::Client.new(cache: { enabled: false })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Monitor Cache Performance
|
|
79
|
+
|
|
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.
|
|
90
|
+
|
|
39
91
|
## Usage
|
|
40
92
|
|
|
41
93
|
### Command Line
|
|
@@ -112,11 +164,137 @@ end
|
|
|
112
164
|
- ✓ Token prices
|
|
113
165
|
- ✓ Portfolio tracking
|
|
114
166
|
- ✓ Multi-network support (mainnet, shasta, nile)
|
|
167
|
+
- ✓ Intelligent caching with TTL and stale-while-revalidate
|
|
168
|
+
- ✓ Cache statistics and monitoring
|
|
115
169
|
- ✓ Clean, simple output
|
|
116
170
|
- ✓ Proper decimal formatting
|
|
117
171
|
- ✓ Environment variable support
|
|
118
172
|
- ✓ Rate limit management with API keys
|
|
119
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
|
|
190
|
+
|
|
191
|
+
### Cache Configuration
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
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
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Endpoint-Specific TTL Values
|
|
207
|
+
|
|
208
|
+
Different endpoints have optimized TTL values based on data volatility:
|
|
209
|
+
|
|
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 |
|
|
217
|
+
|
|
218
|
+
### Cache Statistics
|
|
219
|
+
|
|
220
|
+
Monitor cache performance to optimize your configuration:
|
|
221
|
+
|
|
222
|
+
```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
|
|
254
|
+
|
|
255
|
+
# Get cache size
|
|
256
|
+
puts "Cache has #{Tron::Cache.size} entries"
|
|
257
|
+
|
|
258
|
+
# Reset statistics (useful for testing)
|
|
259
|
+
Tron::Cache.reset_stats
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Advanced Cache Usage
|
|
263
|
+
|
|
264
|
+
#### Disable Caching for Specific Requests
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
# Disable cache for a specific HTTP request
|
|
268
|
+
response = Tron::Utils::HTTP.get(url, headers, { enabled: false })
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### Custom TTL for Specific Requests
|
|
272
|
+
|
|
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
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Use Endpoint-Specific TTL
|
|
282
|
+
|
|
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
|
+
})
|
|
288
|
+
```
|
|
289
|
+
|
|
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
|
+
|
|
120
298
|
## Services Architecture
|
|
121
299
|
|
|
122
300
|
The gem is organized into modular services:
|
|
@@ -124,8 +302,9 @@ The gem is organized into modular services:
|
|
|
124
302
|
- `Tron::Services::Balance` - TRX and TRC20 token balances
|
|
125
303
|
- `Tron::Services::Resources` - Account resources (bandwidth/energy)
|
|
126
304
|
- `Tron::Services::Price` - Token price information
|
|
127
|
-
- `Tron::Utils::HTTP` - HTTP client with error handling
|
|
305
|
+
- `Tron::Utils::HTTP` - HTTP client with error handling and caching
|
|
128
306
|
- `Tron::Utils::Address` - TRON address validation and conversion
|
|
307
|
+
- `Tron::Cache` - Thread-safe caching with TTL and statistics
|
|
129
308
|
|
|
130
309
|
## API Reference
|
|
131
310
|
|
data/lib/tron/cache.rb
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
|
|
3
|
+
module Tron
|
|
4
|
+
# Thread-safe, global cache implementation for TRON wallet toolkit
|
|
5
|
+
#
|
|
6
|
+
# This cache provides a simple key-value store with time-based expiration and
|
|
7
|
+
# failure fallback capabilities. It's designed specifically for caching API
|
|
8
|
+
# responses to improve performance and reduce rate limit issues.
|
|
9
|
+
#
|
|
10
|
+
# Features:
|
|
11
|
+
# - Thread-safe operations using Mutex synchronization
|
|
12
|
+
# - Time-based expiration with TTL (time-to-live) and max_stale settings
|
|
13
|
+
# - Fallback behavior when cache refresh fails
|
|
14
|
+
# - Global storage shared across all instances
|
|
15
|
+
# - Cache statistics tracking (hits, misses)
|
|
16
|
+
#
|
|
17
|
+
# Example usage:
|
|
18
|
+
# Tron::Cache.fetch("wallet_balance_#{address}", ttl: 300, max_stale: 600) do
|
|
19
|
+
# api_client.get_wallet_balance(address)
|
|
20
|
+
# end
|
|
21
|
+
class Cache
|
|
22
|
+
# Class-level storage for global cache
|
|
23
|
+
@@store = {}
|
|
24
|
+
@@mutex = Mutex.new
|
|
25
|
+
# Global statistics
|
|
26
|
+
@@global_stats = { total_hits: 0, total_misses: 0, total_fetches: 0 }
|
|
27
|
+
|
|
28
|
+
# Fetches a value from cache or executes the block to generate and cache it
|
|
29
|
+
#
|
|
30
|
+
# The fetch method implements a time-based caching strategy with fallback:
|
|
31
|
+
# 1. If entry exists and is fresh (age < ttl) → return cached value
|
|
32
|
+
# 2. If entry exists but stale (age > ttl but < max_stale) → try to update:
|
|
33
|
+
# - If block succeeds: cache new value and return it
|
|
34
|
+
# - If block raises error: return stale value (fallback)
|
|
35
|
+
# 3. If no cache or too old → execute block, cache result, return it
|
|
36
|
+
#
|
|
37
|
+
# @param key [String] the cache key (should be unique for the data being cached)
|
|
38
|
+
# @param ttl [Integer] time-to-live in seconds (how long to consider value fresh)
|
|
39
|
+
# @param max_stale [Integer] maximum stale time in seconds (how long to keep stale values)
|
|
40
|
+
# @yield [Proc] block to execute if value is not cached or expired
|
|
41
|
+
# @return [Object] the cached value or result from the block
|
|
42
|
+
# @raise [ArgumentError] if no block is provided
|
|
43
|
+
def self.fetch(key, ttl:, max_stale:, &block)
|
|
44
|
+
raise ArgumentError, "Block required for cache fetch" unless block_given?
|
|
45
|
+
|
|
46
|
+
@@mutex.synchronize do
|
|
47
|
+
entry = @@store[key]
|
|
48
|
+
@@global_stats[:total_fetches] += 1
|
|
49
|
+
|
|
50
|
+
if entry && Time.now - entry[:cached_at] < ttl
|
|
51
|
+
# Cache is fresh - return cached value
|
|
52
|
+
# Increment hit counter if present
|
|
53
|
+
@@store[key][:hits] = (entry[:hits] || 0) + 1
|
|
54
|
+
@@global_stats[:total_hits] += 1
|
|
55
|
+
return entry[:value]
|
|
56
|
+
elsif entry && Time.now - entry[:cached_at] < max_stale
|
|
57
|
+
# Cache is stale but within max_stale - try to update with fallback
|
|
58
|
+
begin
|
|
59
|
+
new_value = yield
|
|
60
|
+
@@store[key] = {
|
|
61
|
+
value: new_value,
|
|
62
|
+
cached_at: Time.now,
|
|
63
|
+
ttl: ttl,
|
|
64
|
+
max_stale: max_stale,
|
|
65
|
+
hits: 0,
|
|
66
|
+
misses: 0
|
|
67
|
+
}
|
|
68
|
+
@@global_stats[:total_misses] += 1
|
|
69
|
+
return new_value
|
|
70
|
+
rescue
|
|
71
|
+
# Return stale value as fallback
|
|
72
|
+
# Increment miss counter since the block failed
|
|
73
|
+
@@store[key][:misses] = (entry[:misses] || 0) + 1
|
|
74
|
+
@@global_stats[:total_hits] += 1 # Stale hit is still a hit
|
|
75
|
+
return entry[:value]
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
# No cache or too old - execute block and cache result
|
|
79
|
+
new_value = yield
|
|
80
|
+
@@store[key] = {
|
|
81
|
+
value: new_value,
|
|
82
|
+
cached_at: Time.now,
|
|
83
|
+
ttl: ttl,
|
|
84
|
+
max_stale: max_stale,
|
|
85
|
+
hits: 0,
|
|
86
|
+
misses: 0
|
|
87
|
+
}
|
|
88
|
+
@@global_stats[:total_misses] += 1
|
|
89
|
+
return new_value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Checks if a key exists in the cache (without considering expiration)
|
|
95
|
+
#
|
|
96
|
+
# @param key [String] the cache key
|
|
97
|
+
# @return [Boolean] true if key exists in the cache, false otherwise
|
|
98
|
+
def self.exists?(key)
|
|
99
|
+
@@mutex.synchronize do
|
|
100
|
+
@@store.key?(key)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Deletes a specific key from the cache
|
|
105
|
+
#
|
|
106
|
+
# @param key [String] the cache key to delete
|
|
107
|
+
# @return [void]
|
|
108
|
+
def self.delete(key)
|
|
109
|
+
@@mutex.synchronize do
|
|
110
|
+
@@store.delete(key)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Clears all cached entries
|
|
115
|
+
#
|
|
116
|
+
# This is useful for testing or when a full cache refresh is needed.
|
|
117
|
+
# @return [void]
|
|
118
|
+
def self.clear
|
|
119
|
+
@@mutex.synchronize do
|
|
120
|
+
@@store.clear
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Returns the number of cached entries
|
|
125
|
+
# @return [Integer] number of cached entries
|
|
126
|
+
def self.size
|
|
127
|
+
@@mutex.synchronize do
|
|
128
|
+
@@store.size
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Returns cache statistics for a specific key
|
|
133
|
+
#
|
|
134
|
+
# @param key [String] the cache key
|
|
135
|
+
# @return [Hash, nil] statistics hash if key exists, nil otherwise
|
|
136
|
+
def self.stats(key)
|
|
137
|
+
@@mutex.synchronize do
|
|
138
|
+
entry = @@store[key]
|
|
139
|
+
return nil unless entry
|
|
140
|
+
|
|
141
|
+
{
|
|
142
|
+
hits: entry[:hits] || 0,
|
|
143
|
+
misses: entry[:misses] || 0,
|
|
144
|
+
cached_at: entry[:cached_at],
|
|
145
|
+
expires_at: entry[:cached_at] + entry[:ttl],
|
|
146
|
+
ttl: entry[:ttl],
|
|
147
|
+
max_stale: entry[:max_stale],
|
|
148
|
+
expired: Time.now - entry[:cached_at] >= entry[:ttl]
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Returns global cache statistics across all keys
|
|
154
|
+
#
|
|
155
|
+
# This provides an overall view of cache performance including hit rate.
|
|
156
|
+
# @return [Hash] global statistics including total_hits, total_misses, hit_rate
|
|
157
|
+
def self.global_stats
|
|
158
|
+
@@mutex.synchronize do
|
|
159
|
+
total_requests = @@global_stats[:total_fetches]
|
|
160
|
+
hit_rate = if total_requests > 0
|
|
161
|
+
(@@global_stats[:total_hits].to_f / total_requests * 100).round(2)
|
|
162
|
+
else
|
|
163
|
+
0.0
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
{
|
|
167
|
+
total_hits: @@global_stats[:total_hits],
|
|
168
|
+
total_misses: @@global_stats[:total_misses],
|
|
169
|
+
total_fetches: @@global_stats[:total_fetches],
|
|
170
|
+
hit_rate_percentage: hit_rate,
|
|
171
|
+
cache_size: @@store.size,
|
|
172
|
+
memory_keys: @@store.keys.size
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Reset global statistics
|
|
178
|
+
# Useful for testing or when you want to start fresh statistics
|
|
179
|
+
# @return [void]
|
|
180
|
+
def self.reset_stats
|
|
181
|
+
@@mutex.synchronize do
|
|
182
|
+
@@global_stats = { total_hits: 0, total_misses: 0, total_fetches: 0 }
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
data/lib/tron/client.rb
CHANGED
|
@@ -120,6 +120,22 @@ module Tron
|
|
|
120
120
|
}
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
def cache_enabled?
|
|
124
|
+
configuration.cache_enabled
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def cache_stats
|
|
128
|
+
{
|
|
129
|
+
price: price_service.cache_stats,
|
|
130
|
+
balance: balance_service.cache_stats
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def clear_cache
|
|
135
|
+
price_service.clear_cache
|
|
136
|
+
balance_service.clear_cache
|
|
137
|
+
end
|
|
138
|
+
|
|
123
139
|
private
|
|
124
140
|
|
|
125
141
|
def validate_address!(address)
|
data/lib/tron/configuration.rb
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# lib/tron/configuration.rb
|
|
2
2
|
module Tron
|
|
3
3
|
class Configuration
|
|
4
|
-
attr_accessor :api_key, :tronscan_api_key, :
|
|
4
|
+
attr_accessor :api_key, :tronscan_api_key, :timeout, :base_url, :tronscan_base_url, :strict_mode
|
|
5
|
+
attr_accessor :cache_enabled, :cache_ttl, :cache_max_stale
|
|
6
|
+
attr_reader :network
|
|
5
7
|
|
|
6
8
|
def initialize
|
|
7
9
|
@network = :mainnet
|
|
8
10
|
@timeout = 30
|
|
9
11
|
@strict_mode = false
|
|
12
|
+
# Cache configuration defaults
|
|
13
|
+
@cache_enabled = true
|
|
14
|
+
@cache_ttl = 300 # 5 minutes default TTL
|
|
15
|
+
@cache_max_stale = 600 # 10 minutes max stale
|
|
10
16
|
setup_urls
|
|
11
17
|
end
|
|
12
18
|
|
|
@@ -15,6 +21,16 @@ module Tron
|
|
|
15
21
|
setup_urls
|
|
16
22
|
end
|
|
17
23
|
|
|
24
|
+
def cache=(options)
|
|
25
|
+
if options.is_a?(Hash)
|
|
26
|
+
@cache_enabled = options.fetch(:enabled, true)
|
|
27
|
+
@cache_ttl = options.fetch(:ttl, 300)
|
|
28
|
+
@cache_max_stale = options.fetch(:max_stale, 600)
|
|
29
|
+
elsif options == false
|
|
30
|
+
@cache_enabled = false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
18
34
|
private
|
|
19
35
|
|
|
20
36
|
def setup_urls
|
|
@@ -1,50 +1,87 @@
|
|
|
1
1
|
# lib/tron/services/balance.rb
|
|
2
2
|
require_relative '../utils/http'
|
|
3
3
|
require_relative '../utils/address'
|
|
4
|
+
require_relative '../utils/cache'
|
|
5
|
+
require_relative '../utils/rate_limiter'
|
|
4
6
|
|
|
5
7
|
module Tron
|
|
6
8
|
module Services
|
|
7
9
|
class Balance
|
|
8
10
|
def initialize(config)
|
|
9
11
|
@config = config
|
|
12
|
+
@cache = Utils::Cache.new(max_age: config.cache_ttl) if config.cache_enabled
|
|
13
|
+
@rate_limiter = Utils::RateLimiter.new(max_requests: 1, time_window: 1.0)
|
|
14
|
+
@cache_hits = 0
|
|
15
|
+
@cache_misses = 0
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def get_trx(address)
|
|
13
19
|
validate_address!(address)
|
|
14
|
-
|
|
20
|
+
|
|
21
|
+
cache_key = "balance:trx:#{address}:#{@config.network}"
|
|
22
|
+
|
|
23
|
+
# Check cache first
|
|
24
|
+
if @config.cache_enabled && (cached = @cache.get(cache_key))
|
|
25
|
+
@cache_hits += 1
|
|
26
|
+
return cached
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@cache_misses += 1
|
|
30
|
+
|
|
31
|
+
# Rate limit before API call
|
|
32
|
+
@rate_limiter.execute_request
|
|
33
|
+
|
|
15
34
|
url = "#{@config.base_url}/v1/accounts/#{address}"
|
|
16
35
|
headers = api_headers
|
|
17
|
-
|
|
36
|
+
|
|
18
37
|
response = Utils::HTTP.get(url, headers)
|
|
19
|
-
|
|
38
|
+
|
|
20
39
|
# Validate response structure
|
|
21
40
|
raise "Unexpected API response format" unless response.is_a?(Hash)
|
|
22
41
|
raise "Missing 'data' field in response" unless response.key?('data')
|
|
23
42
|
raise "Invalid 'data' format in response" unless response['data'].is_a?(Array)
|
|
24
43
|
raise "Empty account data in response" if response['data'].empty?
|
|
25
|
-
|
|
44
|
+
|
|
26
45
|
account_data = response['data'].first
|
|
27
46
|
raise "Invalid account data format" unless account_data.is_a?(Hash)
|
|
28
|
-
|
|
47
|
+
|
|
29
48
|
# The balance field is only present when > 0; defaults to 0 for new/empty accounts
|
|
30
49
|
raw_balance = account_data.fetch('balance', 0)
|
|
31
|
-
format_balance(raw_balance, 6)
|
|
50
|
+
result = format_balance(raw_balance, 6)
|
|
51
|
+
|
|
52
|
+
# Cache the result
|
|
53
|
+
@cache.set(cache_key, result) if @config.cache_enabled
|
|
54
|
+
|
|
55
|
+
result
|
|
32
56
|
end
|
|
33
57
|
|
|
34
58
|
def get_trc20_tokens(address, strict: false)
|
|
35
59
|
validate_address!(address)
|
|
36
|
-
|
|
60
|
+
|
|
61
|
+
cache_key = "balance:trc20:#{address}:#{@config.network}"
|
|
62
|
+
|
|
63
|
+
# Check cache first
|
|
64
|
+
if @config.cache_enabled && (cached = @cache.get(cache_key))
|
|
65
|
+
@cache_hits += 1
|
|
66
|
+
return cached
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@cache_misses += 1
|
|
70
|
+
|
|
71
|
+
# Rate limit before API call
|
|
72
|
+
@rate_limiter.execute_request
|
|
73
|
+
|
|
37
74
|
url = "#{@config.tronscan_base_url}/api/account/wallet?address=#{address}&asset_type=1"
|
|
38
75
|
headers = tronscan_headers
|
|
39
|
-
|
|
76
|
+
|
|
40
77
|
response = Utils::HTTP.get(url, headers)
|
|
41
|
-
|
|
78
|
+
|
|
42
79
|
# Validate response structure
|
|
43
80
|
raise "Unexpected API response format for TRC20 tokens" unless response.is_a?(Hash)
|
|
44
81
|
raise "Missing 'data' field in TRC20 response" unless response.key?('data')
|
|
45
82
|
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 }
|
|
83
|
+
|
|
84
|
+
result = response['data'].select { |token| token['token_type'] == 20 && token['balance'].to_f > 0 }
|
|
48
85
|
.map do |token|
|
|
49
86
|
validate_token_data!(token)
|
|
50
87
|
{
|
|
@@ -55,6 +92,27 @@ module Tron
|
|
|
55
92
|
address: token['token_id']
|
|
56
93
|
}
|
|
57
94
|
end
|
|
95
|
+
|
|
96
|
+
# Cache the result
|
|
97
|
+
@cache.set(cache_key, result) if @config.cache_enabled
|
|
98
|
+
|
|
99
|
+
result
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def cache_stats
|
|
103
|
+
total = @cache_hits + @cache_misses
|
|
104
|
+
{
|
|
105
|
+
hits: @cache_hits,
|
|
106
|
+
misses: @cache_misses,
|
|
107
|
+
total: total,
|
|
108
|
+
hit_rate: total > 0 ? (@cache_hits.to_f / total * 100).round(2) : 0.0
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def clear_cache
|
|
113
|
+
@cache&.clear
|
|
114
|
+
@cache_hits = 0
|
|
115
|
+
@cache_misses = 0
|
|
58
116
|
end
|
|
59
117
|
|
|
60
118
|
private
|
data/lib/tron/services/price.rb
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
# lib/tron/services/price.rb
|
|
2
2
|
require_relative '../utils/http'
|
|
3
|
+
require_relative '../utils/cache'
|
|
4
|
+
require_relative '../utils/rate_limiter'
|
|
3
5
|
|
|
4
6
|
module Tron
|
|
5
7
|
module Services
|
|
6
8
|
class Price
|
|
7
9
|
def initialize(config)
|
|
8
10
|
@config = config
|
|
11
|
+
@cache = Utils::Cache.new(max_age: config.cache_ttl) if config.cache_enabled
|
|
12
|
+
@rate_limiter = Utils::RateLimiter.new(max_requests: 1, time_window: 1.0)
|
|
13
|
+
@cache_hits = 0
|
|
14
|
+
@cache_misses = 0
|
|
9
15
|
end
|
|
10
16
|
|
|
11
17
|
def get_token_price(token = 'trx')
|
|
18
|
+
cache_key = cache_key_for(token)
|
|
19
|
+
|
|
20
|
+
# Check cache first
|
|
21
|
+
if @config.cache_enabled && (cached = @cache.get(cache_key))
|
|
22
|
+
@cache_hits += 1
|
|
23
|
+
return cached
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@cache_misses += 1
|
|
27
|
+
|
|
28
|
+
# Rate limit before API call
|
|
29
|
+
@rate_limiter.execute_request
|
|
30
|
+
|
|
12
31
|
url = "#{@config.tronscan_base_url}/api/token/price?token=#{token}"
|
|
13
32
|
headers = tronscan_headers
|
|
14
|
-
|
|
33
|
+
|
|
15
34
|
response = Utils::HTTP.get(url, headers)
|
|
16
|
-
|
|
35
|
+
|
|
17
36
|
# Validate response structure
|
|
18
37
|
unless response.is_a?(Hash)
|
|
19
38
|
raise "Unexpected API response format for token price"
|
|
20
39
|
end
|
|
21
|
-
|
|
40
|
+
|
|
41
|
+
# Cache the successful response
|
|
42
|
+
@cache.set(cache_key, response) if @config.cache_enabled
|
|
43
|
+
|
|
22
44
|
response
|
|
23
45
|
rescue => e
|
|
46
|
+
# Serve stale data if available
|
|
47
|
+
if @config.cache_enabled && cached
|
|
48
|
+
warn "Warning: API error for #{token}, serving stale cache: #{e.message}"
|
|
49
|
+
return cached
|
|
50
|
+
end
|
|
51
|
+
|
|
24
52
|
if @config.strict_mode
|
|
25
53
|
raise e
|
|
26
54
|
else
|
|
@@ -51,16 +79,30 @@ module Tron
|
|
|
51
79
|
end
|
|
52
80
|
|
|
53
81
|
def get_token_price_usd(token)
|
|
82
|
+
cache_key = "#{cache_key_for(token)}:usd"
|
|
83
|
+
|
|
84
|
+
# Check cache first for the USD price directly
|
|
85
|
+
if @config.cache_enabled && (cached = @cache.get(cache_key))
|
|
86
|
+
@cache_hits += 1
|
|
87
|
+
return cached
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
@cache_misses += 1
|
|
54
91
|
price_data = get_token_price(token)
|
|
55
92
|
return nil unless price_data.is_a?(Hash)
|
|
56
|
-
|
|
57
|
-
if price_data['price_in_usd']
|
|
93
|
+
|
|
94
|
+
result = if price_data['price_in_usd']
|
|
58
95
|
price_data['price_in_usd'].to_f
|
|
59
96
|
elsif price_data['priceInUsd']
|
|
60
97
|
price_data['priceInUsd'].to_f
|
|
61
98
|
else
|
|
62
99
|
nil
|
|
63
100
|
end
|
|
101
|
+
|
|
102
|
+
# Cache the USD price
|
|
103
|
+
@cache.set(cache_key, result) if @config.cache_enabled && result
|
|
104
|
+
|
|
105
|
+
result
|
|
64
106
|
end
|
|
65
107
|
|
|
66
108
|
def get_token_value_usd(balance, token)
|
|
@@ -104,8 +146,28 @@ module Tron
|
|
|
104
146
|
end
|
|
105
147
|
end
|
|
106
148
|
|
|
149
|
+
def cache_stats
|
|
150
|
+
total = @cache_hits + @cache_misses
|
|
151
|
+
{
|
|
152
|
+
hits: @cache_hits,
|
|
153
|
+
misses: @cache_misses,
|
|
154
|
+
total: total,
|
|
155
|
+
hit_rate: total > 0 ? (@cache_hits.to_f / total * 100).round(2) : 0.0
|
|
156
|
+
}
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def clear_cache
|
|
160
|
+
@cache&.clear
|
|
161
|
+
@cache_hits = 0
|
|
162
|
+
@cache_misses = 0
|
|
163
|
+
end
|
|
164
|
+
|
|
107
165
|
private
|
|
108
166
|
|
|
167
|
+
def cache_key_for(token)
|
|
168
|
+
"price:#{token.downcase}:#{@config.network}"
|
|
169
|
+
end
|
|
170
|
+
|
|
109
171
|
def tronscan_headers
|
|
110
172
|
headers = { 'accept' => 'application/json' }
|
|
111
173
|
headers['TRON-PRO-API-KEY'] = @config.tronscan_api_key if @config.tronscan_api_key
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# lib/tron/utils/cache.rb
|
|
2
|
+
require 'monitor'
|
|
3
|
+
|
|
4
|
+
module Tron
|
|
5
|
+
module Utils
|
|
6
|
+
class Cache
|
|
7
|
+
include MonitorMixin
|
|
8
|
+
|
|
9
|
+
def initialize(max_age: 300) # 5 minutes default
|
|
10
|
+
super()
|
|
11
|
+
@cache = {}
|
|
12
|
+
@timestamps = {}
|
|
13
|
+
@max_age = max_age
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(key)
|
|
17
|
+
synchronize do
|
|
18
|
+
cleanup_expired_entries
|
|
19
|
+
|
|
20
|
+
if @cache.key?(key)
|
|
21
|
+
@cache[key]
|
|
22
|
+
else
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set(key, value)
|
|
29
|
+
synchronize do
|
|
30
|
+
@cache[key] = value
|
|
31
|
+
@timestamps[key] = Time.now.to_f
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(key)
|
|
36
|
+
synchronize do
|
|
37
|
+
@cache.delete(key)
|
|
38
|
+
@timestamps.delete(key)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def clear
|
|
43
|
+
synchronize do
|
|
44
|
+
@cache.clear
|
|
45
|
+
@timestamps.clear
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def cleanup_expired_entries
|
|
52
|
+
now = Time.now.to_f
|
|
53
|
+
expired_keys = @timestamps.select { |_, timestamp| now - timestamp > @max_age }.keys
|
|
54
|
+
expired_keys.each do |key|
|
|
55
|
+
@cache.delete(key)
|
|
56
|
+
@timestamps.delete(key)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/tron/utils/http.rb
CHANGED
|
@@ -2,20 +2,116 @@
|
|
|
2
2
|
require 'net/http'
|
|
3
3
|
require 'uri'
|
|
4
4
|
require 'json'
|
|
5
|
+
require 'digest'
|
|
5
6
|
|
|
6
7
|
module Tron
|
|
7
8
|
module Utils
|
|
8
9
|
class HTTP
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
# TTL values for different endpoint types (in seconds)
|
|
11
|
+
# These are optimized based on data volatility
|
|
12
|
+
ENDPOINT_TTL = {
|
|
13
|
+
# Balance endpoints - moderate volatility (5 minutes)
|
|
14
|
+
balance: { ttl: 300, max_stale: 600 },
|
|
15
|
+
|
|
16
|
+
# Token info endpoints - low volatility (15 minutes)
|
|
17
|
+
token_info: { ttl: 900, max_stale: 1800 },
|
|
18
|
+
|
|
19
|
+
# Price endpoints - high volatility (1 minute)
|
|
20
|
+
price: { ttl: 60, max_stale: 120 },
|
|
21
|
+
|
|
22
|
+
# Account resources - moderate volatility (5 minutes)
|
|
23
|
+
resources: { ttl: 300, max_stale: 600 },
|
|
24
|
+
|
|
25
|
+
# Default for unclassified endpoints
|
|
26
|
+
default: { ttl: 300, max_stale: 600 }
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# GET request with optional caching
|
|
30
|
+
# @param url [String] the URL to request
|
|
31
|
+
# @param headers [Hash] request headers
|
|
32
|
+
# @param cache_options [Hash] optional cache configuration
|
|
33
|
+
# @option cache_options [Boolean] :enabled override global cache setting
|
|
34
|
+
# @option cache_options [Integer] :ttl custom TTL in seconds
|
|
35
|
+
# @option cache_options [Integer] :max_stale custom max_stale in seconds
|
|
36
|
+
# @option cache_options [Symbol] :endpoint_type endpoint type for TTL lookup
|
|
37
|
+
def self.get(url, headers = {}, cache_options = {})
|
|
38
|
+
if should_use_cache?(cache_options)
|
|
39
|
+
cache_key = generate_cache_key('GET', url, headers)
|
|
40
|
+
ttl_config = get_ttl_config(cache_options)
|
|
41
|
+
|
|
42
|
+
Tron::Cache.fetch(cache_key, ttl: ttl_config[:ttl], max_stale: ttl_config[:max_stale]) do
|
|
43
|
+
make_request(Net::HTTP::Get, url, nil, headers)
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
make_request(Net::HTTP::Get, url, nil, headers)
|
|
47
|
+
end
|
|
11
48
|
end
|
|
12
49
|
|
|
50
|
+
# POST request (no caching for mutations)
|
|
13
51
|
def self.post(url, body = nil, headers = {})
|
|
14
52
|
make_request(Net::HTTP::Post, url, body, headers)
|
|
15
53
|
end
|
|
16
54
|
|
|
55
|
+
# Clear cache for a specific endpoint
|
|
56
|
+
# @param url [String] the URL to clear from cache
|
|
57
|
+
# @param headers [Hash] request headers used in the original request
|
|
58
|
+
def self.clear_cache(url, headers = {})
|
|
59
|
+
cache_key = generate_cache_key('GET', url, headers)
|
|
60
|
+
Tron::Cache.delete(cache_key)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get cache statistics for an endpoint
|
|
64
|
+
# @param url [String] the URL to get stats for
|
|
65
|
+
# @param headers [Hash] request headers used in the original request
|
|
66
|
+
def self.cache_stats(url, headers = {})
|
|
67
|
+
cache_key = generate_cache_key('GET', url, headers)
|
|
68
|
+
Tron::Cache.stats(cache_key)
|
|
69
|
+
end
|
|
70
|
+
|
|
17
71
|
private
|
|
18
72
|
|
|
73
|
+
# Determine if caching should be used for this request
|
|
74
|
+
def self.should_use_cache?(cache_options)
|
|
75
|
+
# Check if caching is explicitly disabled for this request
|
|
76
|
+
return false if cache_options[:enabled] == false
|
|
77
|
+
|
|
78
|
+
# Otherwise, use global configuration
|
|
79
|
+
Tron.configuration.cache_enabled
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get TTL configuration for the request
|
|
83
|
+
def self.get_ttl_config(cache_options)
|
|
84
|
+
# If custom TTL provided, use it
|
|
85
|
+
if cache_options[:ttl] && cache_options[:max_stale]
|
|
86
|
+
return { ttl: cache_options[:ttl], max_stale: cache_options[:max_stale] }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# If endpoint type specified, use its TTL
|
|
90
|
+
if cache_options[:endpoint_type] && ENDPOINT_TTL[cache_options[:endpoint_type]]
|
|
91
|
+
return ENDPOINT_TTL[cache_options[:endpoint_type]]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Use global configuration if set
|
|
95
|
+
if Tron.configuration.cache_ttl && Tron.configuration.cache_max_stale
|
|
96
|
+
return {
|
|
97
|
+
ttl: Tron.configuration.cache_ttl,
|
|
98
|
+
max_stale: Tron.configuration.cache_max_stale
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Fall back to default
|
|
103
|
+
ENDPOINT_TTL[:default]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Generate a unique cache key based on request parameters
|
|
107
|
+
def self.generate_cache_key(method, url, headers)
|
|
108
|
+
# Include method, URL, and relevant headers in the cache key
|
|
109
|
+
# Exclude headers that don't affect the response (like User-Agent)
|
|
110
|
+
relevant_headers = headers.reject { |k, _| k.to_s.downcase == 'user-agent' }
|
|
111
|
+
key_parts = [method, url, relevant_headers.sort.to_h]
|
|
112
|
+
Digest::SHA256.hexdigest(key_parts.to_json)
|
|
113
|
+
end
|
|
114
|
+
|
|
19
115
|
def self.make_request(method_class, url, body, headers)
|
|
20
116
|
uri = URI(url)
|
|
21
117
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
@@ -26,7 +122,7 @@ module Tron
|
|
|
26
122
|
request.body = body if body
|
|
27
123
|
|
|
28
124
|
response = http.request(request)
|
|
29
|
-
|
|
125
|
+
|
|
30
126
|
case response
|
|
31
127
|
when Net::HTTPSuccess
|
|
32
128
|
json_response = JSON.parse(response.body)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# lib/tron/utils/rate_limiter.rb
|
|
2
|
+
require 'monitor'
|
|
3
|
+
|
|
4
|
+
module Tron
|
|
5
|
+
module Utils
|
|
6
|
+
class RateLimiter
|
|
7
|
+
include MonitorMixin
|
|
8
|
+
|
|
9
|
+
def initialize(max_requests:, time_window:)
|
|
10
|
+
super()
|
|
11
|
+
@max_requests = max_requests
|
|
12
|
+
@time_window = time_window
|
|
13
|
+
@request_timestamps = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def can_make_request?
|
|
17
|
+
synchronize do
|
|
18
|
+
cleanup_old_requests
|
|
19
|
+
@request_timestamps.length < @max_requests
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def execute_request
|
|
24
|
+
synchronize do
|
|
25
|
+
cleanup_old_requests
|
|
26
|
+
|
|
27
|
+
if @request_timestamps.length >= @max_requests
|
|
28
|
+
# Calculate sleep time until oldest request exits the time window
|
|
29
|
+
oldest_time = @request_timestamps.first
|
|
30
|
+
sleep_time = @time_window - (Time.now.to_f - oldest_time)
|
|
31
|
+
sleep(sleep_time) if sleep_time > 0
|
|
32
|
+
cleanup_old_requests
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@request_timestamps << Time.now.to_f
|
|
36
|
+
# Return the time until next allowed request
|
|
37
|
+
calculate_time_to_next_request
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def cleanup_old_requests
|
|
44
|
+
now = Time.now.to_f
|
|
45
|
+
@request_timestamps.reject! { |timestamp| now - timestamp > @time_window }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def calculate_time_to_next_request
|
|
49
|
+
if @request_timestamps.length <= 1
|
|
50
|
+
0
|
|
51
|
+
else
|
|
52
|
+
now = Time.now.to_f
|
|
53
|
+
oldest_in_window = now - @time_window
|
|
54
|
+
next_available = @request_timestamps.find { |ts| ts > oldest_in_window }
|
|
55
|
+
next_available ? [0, @time_window - (now - next_available)].max : 0
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/tron/version.rb
CHANGED
data/lib/tron.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tron.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Your Name
|
|
@@ -93,13 +93,16 @@ files:
|
|
|
93
93
|
- README.md
|
|
94
94
|
- bin/tron-wallet
|
|
95
95
|
- lib/tron.rb
|
|
96
|
+
- lib/tron/cache.rb
|
|
96
97
|
- lib/tron/client.rb
|
|
97
98
|
- lib/tron/configuration.rb
|
|
98
99
|
- lib/tron/services/balance.rb
|
|
99
100
|
- lib/tron/services/price.rb
|
|
100
101
|
- lib/tron/services/resources.rb
|
|
101
102
|
- lib/tron/utils/address.rb
|
|
103
|
+
- lib/tron/utils/cache.rb
|
|
102
104
|
- lib/tron/utils/http.rb
|
|
105
|
+
- lib/tron/utils/rate_limiter.rb
|
|
103
106
|
- lib/tron/version.rb
|
|
104
107
|
homepage: https://github.com/yourusername/tron
|
|
105
108
|
licenses:
|