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.
@@ -0,0 +1,295 @@
1
+ # frozen_string_literal: true
2
+ require 'google/protobuf'
3
+
4
+ # This file contains proper Protocol Buffer definitions for TRON transactions
5
+ # These would normally be generated from .proto files, but for this implementation
6
+ # we'll define the essential structures needed for transaction serialization
7
+
8
+ module Tron
9
+ module Protobuf
10
+ # We need to use the google-protobuf gem to define the classes
11
+ # First, let's define a basic structure for TRON transaction serialization
12
+
13
+ # Rather than writing the complex protobuf definitions from scratch,
14
+ # we'll create a helper that properly serializes the transaction according to TRON specs
15
+ class TransactionRawSerializer
16
+ # Field numbers according to TRON's protocol buffer definitions
17
+ REF_BLOCK_BYTES = 1
18
+ # @return [Integer] reference block bytes field number
19
+ REF_BLOCK_NUM = 2
20
+ # @return [Integer] reference block number field number
21
+ REF_BLOCK_HASH = 3
22
+ # @return [Integer] reference block hash field number
23
+ EXPIRATION = 4
24
+ # @return [Integer] expiration field number
25
+ AUTHS = 5 # authority
26
+ # @return [Integer] authority field number
27
+ DATA = 6
28
+ # @return [Integer] data field number
29
+ CONTRACT = 7
30
+ # @return [Integer] contract field number
31
+ SCRIPTS = 8
32
+ # @return [Integer] scripts field number
33
+ FEE_LIMIT = 9
34
+ # @return [Integer] fee limit field number
35
+
36
+ # Contract field numbers
37
+ CONTRACT_TYPE = 1
38
+ # @return [Integer] contract type field number
39
+ CONTRACT_PARAMETER = 2
40
+ # @return [Integer] contract parameter field number
41
+ CONTRACT_PROVIDER = 3
42
+ # @return [Integer] contract provider field number
43
+
44
+ # Serializes a transaction for signing according to TRON's protocol buffer specification
45
+ #
46
+ # @param transaction [Hash] the transaction data to serialize
47
+ # @return [String] the serialized transaction in protocol buffer format
48
+ def self.serialize(transaction)
49
+ raw_data = transaction['raw_data']
50
+ result = []
51
+
52
+ # Serialize each field in the proper order with field numbers
53
+ if raw_data.key?('ref_block_bytes')
54
+ result << encode_field(REF_BLOCK_BYTES, :bytes, convert_hex_to_bytes(raw_data['ref_block_bytes']))
55
+ end
56
+
57
+ if raw_data.key?('ref_block_num')
58
+ result << encode_field(REF_BLOCK_NUM, :varint, raw_data['ref_block_num'])
59
+ end
60
+
61
+ if raw_data.key?('ref_block_hash')
62
+ result << encode_field(REF_BLOCK_HASH, :bytes, convert_hex_to_bytes(raw_data['ref_block_hash']))
63
+ end
64
+
65
+ if raw_data.key?('expiration')
66
+ result << encode_field(EXPIRATION, :varint, raw_data['expiration'])
67
+ end
68
+
69
+ # Add timestamp - TRON has timestamp as part of raw_data
70
+ if raw_data.key?('timestamp')
71
+ result << encode_field(10, :varint, raw_data['timestamp']) # timestamp field number is typically 10
72
+ end
73
+
74
+ if raw_data.key?('fee_limit')
75
+ result << encode_field(FEE_LIMIT, :varint, raw_data['fee_limit'])
76
+ end
77
+
78
+ # Handle contracts - this is more complex
79
+ if raw_data.key?('contract')
80
+ contract_data = raw_data['contract']
81
+ if contract_data.is_a?(Array)
82
+ # TRON allows multiple contracts in one transaction
83
+ contract_data.each do |contract|
84
+ result << encode_field(CONTRACT, :embedded_message, serialize_contract(contract))
85
+ end
86
+ else
87
+ # Single contract
88
+ result << encode_field(CONTRACT, :embedded_message, serialize_contract(contract_data))
89
+ end
90
+ end
91
+
92
+ # Handle data field
93
+ if raw_data.key?('data')
94
+ result << encode_field(DATA, :bytes, convert_hex_to_bytes(raw_data['data']))
95
+ end
96
+
97
+ # Handle auths (authority)
98
+ if raw_data.key?('authority')
99
+ # For now, just handle as bytes or varint depending on the structure
100
+ end
101
+
102
+ result.join
103
+ end
104
+
105
+ private
106
+
107
+ # Encodes a field in protobuf wire format
108
+ #
109
+ # @param field_number [Integer] the field number
110
+ # @param field_type [Symbol] the type of field (:varint, :bytes, :embedded_message)
111
+ # @param value [Object] the value to encode
112
+ # @return [String] the encoded field
113
+ def self.encode_field(field_number, field_type, value)
114
+ # In protobuf, each field is encoded as (field_number << 3 | wire_type) + value
115
+ key = (field_number << 3) | wire_type(field_type)
116
+ varint_bytes = encode_varint(key)
117
+
118
+ case field_type
119
+ when :varint
120
+ varint_bytes + encode_varint(value)
121
+ when :bytes
122
+ varint_bytes + encode_varint(value.length) + value
123
+ when :embedded_message
124
+ varint_bytes + encode_varint(value.length) + value
125
+ else
126
+ varint_bytes + value
127
+ end
128
+ end
129
+
130
+ # Maps field types to protobuf wire types
131
+ #
132
+ # @param field_type [Symbol] the field type
133
+ # @return [Integer] the wire type number
134
+ def self.wire_type(field_type)
135
+ case field_type
136
+ when :varint
137
+ 0 # varint wire type
138
+ when :bytes, :embedded_message
139
+ 2 # length-delimited wire type
140
+ else
141
+ 2 # default to length-delimited
142
+ end
143
+ end
144
+
145
+ # Encodes an integer as a protobuf varint
146
+ #
147
+ # @param value [Integer] the integer to encode
148
+ # @return [String] the encoded varint
149
+ def self.encode_varint(value)
150
+ result = []
151
+ v = value
152
+ loop do
153
+ byte = v & 0x7F
154
+ v >>= 7
155
+ if v == 0
156
+ result << byte
157
+ break
158
+ else
159
+ result << (byte | 0x80)
160
+ end
161
+ end
162
+ result.pack('C*')
163
+ end
164
+
165
+ # Serializes a contract according to TRON's protocol
166
+ #
167
+ # @param contract [Hash] the contract data to serialize
168
+ # @return [String] the serialized contract
169
+ def self.serialize_contract(contract)
170
+ result = []
171
+
172
+ if contract.key?('type')
173
+ # The contract type as a varint
174
+ type_value = contract_type_to_int(contract['type'])
175
+ result << encode_field(CONTRACT_TYPE, :varint, type_value)
176
+ end
177
+
178
+ if contract.key?('parameter')
179
+ # The parameter as embedded message
180
+ param_bytes = serialize_contract_parameter(contract['parameter'])
181
+ result << encode_field(CONTRACT_PARAMETER, :embedded_message, param_bytes)
182
+ end
183
+
184
+ if contract.key?('provider')
185
+ # Provider address as bytes
186
+ result << encode_field(CONTRACT_PROVIDER, :bytes, convert_hex_to_bytes(contract['provider']))
187
+ end
188
+
189
+ result.join
190
+ end
191
+
192
+ # Serializes the parameter part of a contract
193
+ #
194
+ # @param parameter [Hash] the parameter data to serialize
195
+ # @return [String] the serialized parameter
196
+ def self.serialize_contract_parameter(parameter)
197
+ # This is simplified - a full implementation would need to serialize
198
+ # each specific contract type according to its protobuf definition
199
+ # For the triggerSmartContract, this would serialize the function_selector and call_value
200
+
201
+ result = []
202
+
203
+ if parameter.key?('value')
204
+ value = parameter['value']
205
+ if value.is_a?(Hash)
206
+ # Serialize each field in the parameter value
207
+ # This is where we'd need the specific protobuf definition for each contract type
208
+ # For now we'll serialize it in a simplified way that follows protobuf conventions
209
+ value.each do |field_name, field_value|
210
+ field_number = case field_name
211
+ when 'owner_address' then 1
212
+ when 'contract_address' then 2
213
+ when 'data', 'function_selector' then 3
214
+ when 'call_value' then 4
215
+ when 'fee_limit' then 5
216
+ else 1 # Default to 1
217
+ end
218
+
219
+ case field_value
220
+ when String
221
+ # If it's a hex string, convert to bytes
222
+ result << encode_field(field_number, :bytes, convert_hex_to_bytes(field_value))
223
+ when Integer
224
+ result << encode_field(field_number, :varint, field_value)
225
+ else
226
+ # Convert other types appropriately
227
+ result << encode_field(field_number, :bytes, field_value.to_s)
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ # Add type_url field (field number 1 in Any type)
234
+ type_url = parameter['type_url'] || "type.googleapis.com/protocol.#{parameter['type'] || 'TriggerSmartContract'}"
235
+ result << encode_field(1, :string, type_url)
236
+
237
+ result.join
238
+ end
239
+
240
+ # Maps contract type names to their protocol buffer enum values
241
+ #
242
+ # @param type_name [String] the contract type name
243
+ # @return [Integer] the protocol buffer enum value
244
+ def self.contract_type_to_int(type_name)
245
+ type_map = {
246
+ 'AccountCreateContract' => 0,
247
+ 'TransferContract' => 1,
248
+ 'TransferAssetContract' => 2,
249
+ 'VoteAssetContract' => 3,
250
+ 'VoteWitnessContract' => 4,
251
+ 'WitnessCreateContract' => 5,
252
+ 'AssetIssueContract' => 6,
253
+ 'WitnessUpdateContract' => 7,
254
+ 'ParticipateAssetIssueContract' => 8,
255
+ 'AccountUpdateContract' => 9,
256
+ 'FreezeBalanceContract' => 10,
257
+ 'UnfreezeBalanceContract' => 11,
258
+ 'WithdrawBalanceContract' => 12,
259
+ 'UnfreezeAssetContract' => 13,
260
+ 'UpdateAssetContract' => 14,
261
+ 'ProposalCreateContract' => 15,
262
+ 'ProposalApproveContract' => 16,
263
+ 'ProposalDeleteContract' => 17,
264
+ 'SetAccountIdContract' => 18,
265
+ 'CustomContract' => 19,
266
+ 'CreateSmartContract' => 30,
267
+ 'TriggerSmartContract' => 31,
268
+ 'GetContract' => 32,
269
+ 'UpdateSettingContract' => 33,
270
+ 'ExchangeCreateContract' => 41,
271
+ 'ExchangeInjectContract' => 42,
272
+ 'ExchangeWithdrawContract' => 43,
273
+ 'ExchangeTransactionContract' => 44,
274
+ 'UpdateEnergyLimitContract' => 45,
275
+ 'AccountPermissionUpdateContract' => 46
276
+ }
277
+
278
+ type_map[type_name] || 0 # Default to 0 if unknown
279
+ end
280
+
281
+ # Converts a hex string to binary format
282
+ #
283
+ # @param hex_string [String] the hex string to convert
284
+ # @return [String] the binary representation
285
+ def self.convert_hex_to_bytes(hex_string)
286
+ # Remove '0x' prefix if present
287
+ hex = hex_string.to_s
288
+ hex = hex[2..-1] if hex.start_with?('0x', '0X')
289
+ # Pad with leading zero if odd length
290
+ hex = '0' + hex if hex.length.odd?
291
+ [hex].pack('H*')
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require 'google/protobuf'
3
+ require_relative 'protobuf/transaction_raw_serializer'
4
+
5
+ module Tron
6
+ module Protobuf
7
+ # Main class to serialize TRON transactions for signing
8
+ class TransactionSerializer
9
+ # Serializes a transaction for signing
10
+ #
11
+ # @param transaction [Hash] the transaction to serialize
12
+ # @return [String] the serialized transaction
13
+ def self.serialize_for_signing(transaction)
14
+ TransactionRawSerializer.serialize(transaction)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -6,7 +6,11 @@ require_relative '../utils/rate_limiter'
6
6
 
7
7
  module Tron
8
8
  module Services
9
+ # The Balance service handles retrieving TRX and TRC20 token balances for TRON addresses
9
10
  class Balance
11
+ # Creates a new instance of the Balance service
12
+ #
13
+ # @param config [Tron::Configuration] the configuration object
10
14
  def initialize(config)
11
15
  @config = config
12
16
  @cache = Utils::Cache.new(max_age: config.cache_ttl) if config.cache_enabled
@@ -15,6 +19,11 @@ module Tron
15
19
  @cache_misses = 0
16
20
  end
17
21
 
22
+ # Gets the TRX balance for a given address
23
+ #
24
+ # @param address [String] the TRON address to check
25
+ # @return [String] the TRX balance as a formatted string
26
+ # @raise [ArgumentError] if the address is invalid
18
27
  def get_trx(address)
19
28
  validate_address!(address)
20
29
 
@@ -61,6 +70,12 @@ module Tron
61
70
  result
62
71
  end
63
72
 
73
+ # Gets all TRC20 token balances for a given address
74
+ #
75
+ # @param address [String] the TRON address to check
76
+ # @param strict [Boolean] whether to enable strict validation
77
+ # @return [Array<Hash>] an array of token information
78
+ # @raise [ArgumentError] if the address is invalid
64
79
  def get_trc20_tokens(address, strict: false)
65
80
  validate_address!(address)
66
81
 
@@ -105,6 +120,9 @@ module Tron
105
120
  result
106
121
  end
107
122
 
123
+ # Gets cache statistics for the balance service
124
+ #
125
+ # @return [Hash] a hash containing cache statistics
108
126
  def cache_stats
109
127
  total = @cache_hits + @cache_misses
110
128
  {
@@ -115,6 +133,7 @@ module Tron
115
133
  }
116
134
  end
117
135
 
136
+ # Clears the cache for the balance service
118
137
  def clear_cache
119
138
  @cache&.clear
120
139
  @cache_hits = 0
@@ -123,6 +142,10 @@ module Tron
123
142
 
124
143
  private
125
144
 
145
+ # Validates the structure of token data
146
+ #
147
+ # @param token [Hash] the token data to validate
148
+ # @raise [RuntimeError] if the token data is invalid
126
149
  def validate_token_data!(token)
127
150
  unless token.is_a?(Hash)
128
151
  raise "Invalid token data format: expected hash"
@@ -136,6 +159,11 @@ module Tron
136
159
  end
137
160
  end
138
161
 
162
+ # Gets all balance information for a given address
163
+ #
164
+ # @param address [String] the TRON address to check
165
+ # @param strict [Boolean] whether to enable strict validation
166
+ # @return [Hash] a hash containing all balance information
139
167
  def get_all(address, strict: false)
140
168
  validate_address!(address)
141
169
 
@@ -148,10 +176,19 @@ module Tron
148
176
 
149
177
  private
150
178
 
179
+ # Validates a TRON address
180
+ #
181
+ # @param address [String] the address to validate
182
+ # @raise [ArgumentError] if the address is invalid
151
183
  def validate_address!(address)
152
184
  raise ArgumentError, "Invalid TRON address: #{address}" unless Utils::Address.validate(address)
153
185
  end
154
186
 
187
+ # Formats a raw balance with the specified number of decimals
188
+ #
189
+ # @param raw_balance [Integer, String] the raw balance value
190
+ # @param decimals [Integer] the number of decimal places
191
+ # @return [String] the formatted balance
155
192
  def format_balance(raw_balance, decimals)
156
193
  balance = raw_balance.to_i
157
194
  divisor = 10 ** decimals
@@ -160,14 +197,18 @@ module Tron
160
197
  "#{whole}.#{fraction.to_s.rjust(decimals, '0')}"
161
198
  end
162
199
 
163
-
164
-
200
+ # Creates headers for the TRON API
201
+ #
202
+ # @return [Hash] the API headers
165
203
  def api_headers
166
204
  headers = { 'accept' => 'application/json' }
167
205
  headers['TRON-PRO-API-KEY'] = @config.api_key if @config.api_key
168
206
  headers
169
207
  end
170
208
 
209
+ # Creates headers for the Tronscan API
210
+ #
211
+ # @return [Hash] the Tronscan API headers
171
212
  def tronscan_headers
172
213
  headers = { 'accept' => 'application/json' }
173
214
  headers['TRON-PRO-API-KEY'] = @config.tronscan_api_key if @config.tronscan_api_key
@@ -0,0 +1,232 @@
1
+ require_relative '../utils/http'
2
+ require_relative '../utils/address'
3
+ require_relative '../utils/abi'
4
+ require_relative '../abi'
5
+ require_relative 'transaction'
6
+
7
+ module Tron
8
+ module Services
9
+ # The Contract service handles interactions with TRON smart contracts
10
+ # including both read-only calls and state-changing transactions
11
+ class Contract
12
+ # Creates a new instance of the Contract service
13
+ #
14
+ # @param configuration [Tron::Configuration] the configuration object
15
+ def initialize(configuration)
16
+ @configuration = configuration
17
+ @base_url = configuration.base_url
18
+ @transaction_service = Transaction.new(configuration)
19
+ end
20
+
21
+ # Triggers a smart contract (state-changing operation)
22
+ # This creates and broadcasts a transaction to the blockchain
23
+ #
24
+ # @param contract_address [String] the contract address to interact with
25
+ # @param function [String] the function to call on the contract
26
+ # @param parameters [Array] the parameters to pass to the function
27
+ # @param private_key [String] the private key to sign the transaction
28
+ # @param fee_limit [Integer] the maximum energy fee to pay (default: configuration default)
29
+ # @param call_value [Integer] the amount of TRX to send with the call (default: 0)
30
+ # @param owner_address [String] the address of the transaction originator (default: derived from private key)
31
+ # @return [Hash] the transaction result
32
+ def trigger_contract(
33
+ contract_address:,
34
+ function:,
35
+ parameters: [],
36
+ private_key:,
37
+ fee_limit: nil,
38
+ call_value: 0,
39
+ owner_address: nil
40
+ )
41
+ # Use configuration default if fee_limit not provided
42
+ fee_limit ||= @configuration.fee_limit
43
+
44
+ # Derive owner address from private key if not provided
45
+ owner_address ||= derive_address_from_private_key(private_key)
46
+
47
+ # Validate addresses
48
+ validate_address!(contract_address)
49
+ validate_address!(owner_address)
50
+
51
+ # Build transaction
52
+ transaction = build_trigger_transaction(
53
+ contract_address: contract_address,
54
+ function: function,
55
+ parameters: parameters,
56
+ owner_address: owner_address,
57
+ fee_limit: fee_limit,
58
+ call_value: call_value
59
+ )
60
+
61
+ # Sign and broadcast
62
+ @transaction_service.sign_and_broadcast(transaction, private_key)
63
+ end
64
+
65
+ # Calls a smart contract (read-only operation)
66
+ # This does not create a transaction and doesn't change blockchain state
67
+ #
68
+ # @param contract_address [String] the contract address to interact with
69
+ # @param function [String] the function to call on the contract
70
+ # @param parameters [Array] the parameters to pass to the function
71
+ # @param owner_address [String] the address of the caller (default: configuration default)
72
+ # @return [Hash] the result of the function call
73
+ def call_contract(
74
+ contract_address:,
75
+ function:,
76
+ parameters: [],
77
+ owner_address: nil
78
+ )
79
+ # Use a default address if not provided
80
+ owner_address ||= @configuration.default_address || 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb'
81
+
82
+ # Validate address
83
+ validate_address!(contract_address)
84
+
85
+ # Build request
86
+ endpoint = "#{@base_url}/wallet/triggerconstantcontract"
87
+
88
+ # Encode function call
89
+ encoded_data = Utils::ABI.encode_function_call(function, parameters)
90
+
91
+ # Use 'data' field instead of 'function_selector' (same as trigger_contract)
92
+ payload = {
93
+ contract_address: Utils::Address.to_hex(contract_address),
94
+ data: encoded_data,
95
+ owner_address: Utils::Address.to_hex(owner_address)
96
+ }
97
+
98
+ # Make API call
99
+ response = Utils::HTTP.post(endpoint, payload, {
100
+ endpoint_type: :contract_call,
101
+ ttl: 60 # Cache for 1 minute
102
+ })
103
+
104
+ # Parse response
105
+ parse_constant_result(response)
106
+ end
107
+
108
+ # Checks if a payment has been processed (example helper)
109
+ #
110
+ # @param contract_address [String] the contract address
111
+ # @param operator_address [String] the operator's address
112
+ # @param payment_id [String] the payment ID
113
+ # @return [Boolean] true if the payment is processed, false otherwise
114
+ def payment_processed?(contract_address, operator_address, payment_id)
115
+ result = call_contract(
116
+ contract_address: contract_address,
117
+ function: 'isPaymentProcessed(address,bytes16)',
118
+ parameters: [operator_address, payment_id]
119
+ )
120
+
121
+ Utils::ABI.decode_output('bool', result)
122
+ end
123
+
124
+ # Gets the fee destination (example helper)
125
+ #
126
+ # @param contract_address [String] the contract address
127
+ # @param operator_address [String] the operator's address
128
+ # @return [String] the fee destination address
129
+ def get_fee_destination(contract_address, operator_address)
130
+ result = call_contract(
131
+ contract_address: contract_address,
132
+ function: 'getFeeDestination(address)',
133
+ parameters: [operator_address]
134
+ )
135
+
136
+ Utils::ABI.decode_output('address', result)
137
+ end
138
+
139
+ # Checks if an operator is registered (example helper)
140
+ #
141
+ # @param contract_address [String] the contract address
142
+ # @param operator_address [String] the operator's address
143
+ # @return [Boolean] true if the operator is registered, false otherwise
144
+ def operator_registered?(contract_address, operator_address)
145
+ result = call_contract(
146
+ contract_address: contract_address,
147
+ function: 'isOperatorRegistered(address)',
148
+ parameters: [operator_address]
149
+ )
150
+
151
+ Utils::ABI.decode_output('bool', result)
152
+ end
153
+
154
+ private
155
+
156
+ # Builds a trigger transaction for a smart contract call
157
+ #
158
+ # @param contract_address [String] the contract address
159
+ # @param function [String] the function to call
160
+ # @param parameters [Array] the parameters to pass
161
+ # @param owner_address [String] the address of the transaction owner
162
+ # @param fee_limit [Integer] the maximum energy fee
163
+ # @param call_value [Integer] the value to send with the call
164
+ # @return [Hash] the transaction object
165
+ def build_trigger_transaction(
166
+ contract_address:,
167
+ function:,
168
+ parameters:,
169
+ owner_address:,
170
+ fee_limit:,
171
+ call_value:
172
+ )
173
+ endpoint = "#{@base_url}/wallet/triggersmartcontract"
174
+
175
+ # Encode function call (selector + parameters)
176
+ encoded_data = Utils::ABI.encode_function_call(function, parameters)
177
+
178
+ # IMPORTANT: Use 'data' field, not 'function_selector'
179
+ # When function_selector is provided, the API expects it to be the signature string
180
+ # with parameters in a separate 'parameter' field.
181
+ # Using 'data' allows us to send the complete encoded call data.
182
+ payload = {
183
+ contract_address: Utils::Address.to_hex(contract_address),
184
+ data: encoded_data,
185
+ fee_limit: fee_limit,
186
+ call_value: call_value,
187
+ owner_address: Utils::Address.to_hex(owner_address)
188
+ }
189
+
190
+ # Get transaction from API
191
+ response = Utils::HTTP.post(endpoint, payload)
192
+
193
+ raise "Failed to create transaction: #{response}" unless response['result']
194
+
195
+ response['transaction']
196
+ end
197
+
198
+ # Parses the result of a constant function call
199
+ #
200
+ # @param response [Hash] the API response
201
+ # @return [String] the parsed result
202
+ def parse_constant_result(response)
203
+ return nil unless response['result']
204
+
205
+ # Extract constant result (hex string)
206
+ constant_result = response['constant_result']
207
+ return nil if constant_result.nil? || constant_result.empty?
208
+
209
+ constant_result.first
210
+ end
211
+
212
+ # Validates a TRON address
213
+ #
214
+ # @param address [String] the address to validate
215
+ # @raise [ArgumentError] if the address is invalid
216
+ def validate_address!(address)
217
+ require_relative '../utils/address'
218
+ raise ArgumentError, "Invalid TRON address: #{address}" unless Utils::Address.validate(address)
219
+ end
220
+
221
+ # Derives a TRON address from a private key
222
+ #
223
+ # @param private_key [String] the private key in hex format
224
+ # @return [String] the derived address
225
+ def derive_address_from_private_key(private_key)
226
+ # Use the Key class to derive address from private key
227
+ key = Tron::Key.new(priv: private_key)
228
+ key.address
229
+ end
230
+ end
231
+ end
232
+ end