tron.rb 1.1.9 ā 1.2.2
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/lib/tron/abi/constant.rb +16 -0
- data/lib/tron/abi/packed/encoder.rb +177 -0
- data/lib/tron/abi/packed.rb +3 -0
- data/lib/tron/abi/util.rb +5 -4
- data/lib/tron/abi.rb +16 -0
- data/lib/tron/key.rb +26 -0
- data/lib/tron/services/transaction.rb +158 -2
- data/lib/tron/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5cd08e96e3394fc24fb6626f6a36eda41ee19b69c5ae1dc7c80de31cb357464
|
|
4
|
+
data.tar.gz: aed0379a31288c32a74bd7d4aa644c88a5c07899782f853c8a2d0498fe277e01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d127adf6c6f7c3477b4627295725454bfe3e59a409f42d18756d7a6e023d4f0b922c8314cede795a1d291644a309c9a00a03236b206dbec279fc59423d03f8f
|
|
7
|
+
data.tar.gz: ed54942d4329923a49af1f9399157cca3d157b857fe13810e86111707bac6ca645ace01753b1db268cc2f68fce1eeb6b8bfcb7fd22d9c26fdd3486022cb87e8b
|
data/lib/tron/abi/constant.rb
CHANGED
|
@@ -6,6 +6,22 @@ module Tron
|
|
|
6
6
|
module Constant
|
|
7
7
|
extend self
|
|
8
8
|
|
|
9
|
+
# Maximum value for uint256 (2^256 - 1)
|
|
10
|
+
# @return [Integer] maximum value for uint256
|
|
11
|
+
UINT_MAX = 2**256 - 1
|
|
12
|
+
|
|
13
|
+
# Minimum value for uint256
|
|
14
|
+
# @return [Integer] minimum value for uint256
|
|
15
|
+
UINT_MIN = 0
|
|
16
|
+
|
|
17
|
+
# Maximum value for int256 (2^255 - 1)
|
|
18
|
+
# @return [Integer] maximum value for int256
|
|
19
|
+
INT_MAX = 2**255 - 1
|
|
20
|
+
|
|
21
|
+
# Minimum value for int256 (-(2^255))
|
|
22
|
+
# @return [Integer] minimum value for int256
|
|
23
|
+
INT_MIN = -(2**255)
|
|
24
|
+
|
|
9
25
|
# Byte zero constant
|
|
10
26
|
# @return [String] binary string containing zero byte
|
|
11
27
|
BYTE_ZERO = "\x00".b
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../type'
|
|
4
|
+
require_relative '../util'
|
|
5
|
+
require_relative '../constant'
|
|
6
|
+
require_relative '../../utils/address'
|
|
7
|
+
|
|
8
|
+
module Tron
|
|
9
|
+
module Abi
|
|
10
|
+
module Packed
|
|
11
|
+
# Provides a utility module to assist in packed encoding ABIs.
|
|
12
|
+
module Encoder
|
|
13
|
+
extend self
|
|
14
|
+
|
|
15
|
+
# Encodes a specific value in non-standard packed encoding mode.
|
|
16
|
+
#
|
|
17
|
+
# @param type [String] type to be encoded.
|
|
18
|
+
# @param arg [String|Number] value to be encoded.
|
|
19
|
+
# @return [String] the packed encoded type.
|
|
20
|
+
# @raise [EncodingError] if value does not match type.
|
|
21
|
+
# @raise [ArgumentError] if encoding fails for type.
|
|
22
|
+
def type(type, arg)
|
|
23
|
+
case type
|
|
24
|
+
when /^uint(\d+)$/
|
|
25
|
+
uint(arg, $1.to_i / 8)
|
|
26
|
+
when /^int(\d+)$/
|
|
27
|
+
int(arg, $1.to_i / 8)
|
|
28
|
+
when "bool"
|
|
29
|
+
bool(arg)
|
|
30
|
+
when /^ureal(\d+)x(\d+)$/, /^ufixed(\d+)x(\d+)$/
|
|
31
|
+
ufixed(arg, $1.to_i / 8, $2.to_i)
|
|
32
|
+
when /^real(\d+)x(\d+)$/, /^fixed(\d+)x(\d+)$/
|
|
33
|
+
fixed(arg, $1.to_i / 8, $2.to_i)
|
|
34
|
+
when "string"
|
|
35
|
+
string(arg)
|
|
36
|
+
when /^bytes(\d+)$/
|
|
37
|
+
bytes(arg, $1.to_i)
|
|
38
|
+
when "bytes"
|
|
39
|
+
string(arg)
|
|
40
|
+
when /^tuple\((.+)\)$/
|
|
41
|
+
tuple($1.split(","), arg)
|
|
42
|
+
when /^hash(\d+)$/
|
|
43
|
+
hash(arg, $1.to_i / 8)
|
|
44
|
+
when "address"
|
|
45
|
+
address(arg)
|
|
46
|
+
when /^(.+)\[\]$/
|
|
47
|
+
array($1, arg)
|
|
48
|
+
when /^(.+)\[(\d+)\]$/
|
|
49
|
+
fixed_array($1, arg, $2.to_i)
|
|
50
|
+
else
|
|
51
|
+
raise EncodingError, "Unhandled type: #{type}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Properly encodes unsigned integers.
|
|
58
|
+
def uint(value, byte_size)
|
|
59
|
+
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
|
|
60
|
+
raise ValueOutOfBounds, "Number out of range: #{value}" if value > Constant::UINT_MAX or value < Constant::UINT_MIN
|
|
61
|
+
i = value.to_i
|
|
62
|
+
Util.zpad_int(i, byte_size)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Properly encodes signed integers.
|
|
66
|
+
def int(value, byte_size)
|
|
67
|
+
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
|
|
68
|
+
raise ValueOutOfBounds, "Number out of range: #{value}" if value > Constant::INT_MAX or value < Constant::INT_MIN
|
|
69
|
+
real_size = byte_size * 8
|
|
70
|
+
i = value.to_i % 2 ** real_size
|
|
71
|
+
Util.zpad_int(i, byte_size)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Properly encodes booleans.
|
|
75
|
+
def bool(value)
|
|
76
|
+
raise EncodingError, "Argument is not bool: #{value}" unless [TrueClass, FalseClass].any? { |bool_class| value.is_a?(bool_class) }
|
|
77
|
+
(value ? "\x01" : "\x00").b
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Properly encodes unsigned fixed-point numbers.
|
|
81
|
+
def ufixed(value, byte_size, decimals)
|
|
82
|
+
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
|
|
83
|
+
raise ValueOutOfBounds, value unless value >= 0 and value < 2 ** decimals
|
|
84
|
+
scaled_value = (value * (10 ** decimals)).to_i
|
|
85
|
+
uint(scaled_value, byte_size)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Properly encodes signed fixed-point numbers.
|
|
89
|
+
def fixed(value, byte_size, decimals)
|
|
90
|
+
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
|
|
91
|
+
raise ValueOutOfBounds, value unless value >= -2 ** (decimals - 1) and value < 2 ** (decimals - 1)
|
|
92
|
+
scaled_value = (value * (10 ** decimals)).to_i
|
|
93
|
+
int(scaled_value, byte_size)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Properly encodes byte(-string)s.
|
|
97
|
+
def bytes(value, length)
|
|
98
|
+
raise EncodingError, "Expecting String: #{value}" unless value.is_a? String
|
|
99
|
+
value = handle_hex_string(value, length)
|
|
100
|
+
raise ArgumentError, "Value must be a string of length #{length}" unless value.is_a?(String) && value.bytesize == length
|
|
101
|
+
value.b
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Properly encodes (byte-)strings.
|
|
105
|
+
def string(value)
|
|
106
|
+
raise ArgumentError, "Value must be a string" unless value.is_a?(String)
|
|
107
|
+
value.b
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Properly encodes tuples.
|
|
111
|
+
def tuple(types, values)
|
|
112
|
+
# Call the solidity_packed function to handle tuple encoding
|
|
113
|
+
Tron::Abi.solidity_packed(types, values)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Properly encodes hash-strings.
|
|
117
|
+
def hash(value, byte_size)
|
|
118
|
+
raise EncodingError, "Argument too long: #{value}" unless byte_size > 0 and byte_size <= 32
|
|
119
|
+
hash_bytes = handle_hex_string(value, byte_size)
|
|
120
|
+
hash_bytes.b
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Properly encodes addresses.
|
|
124
|
+
def address(value)
|
|
125
|
+
if value.is_a?(String) && value.start_with?('T') && value.length == 34
|
|
126
|
+
# TRON address in Base58 format - convert to hex
|
|
127
|
+
hex_addr = Utils::Address.to_hex(value)
|
|
128
|
+
# Remove the 0x41 prefix and return raw bytes
|
|
129
|
+
Util.hex_to_bin(hex_addr[2..-1])
|
|
130
|
+
elsif value.is_a? Integer
|
|
131
|
+
# address from integer
|
|
132
|
+
Util.zpad_int(value, 20)
|
|
133
|
+
elsif value.size == 20
|
|
134
|
+
# address from raw 20-byte address
|
|
135
|
+
value
|
|
136
|
+
elsif value.size == 40
|
|
137
|
+
# address from hexadecimal address (without 0x prefix)
|
|
138
|
+
Util.hex_to_bin(value)
|
|
139
|
+
elsif value.size == 42 && value[0, 2] == "0x"
|
|
140
|
+
# address from hexadecimal address with 0x prefix
|
|
141
|
+
Util.hex_to_bin(value[2..-1])
|
|
142
|
+
else
|
|
143
|
+
raise EncodingError, "Could not parse address: #{value}"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Properly encodes dynamic-sized arrays.
|
|
148
|
+
def array(type, values)
|
|
149
|
+
# For packed encoding, we don't encode array length, just concatenate values
|
|
150
|
+
values.map { |value| type(type, value) }.join.b
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Properly encodes fixed-size arrays.
|
|
154
|
+
def fixed_array(type, values, size)
|
|
155
|
+
raise ArgumentError, "Array size does not match" unless values.size == size
|
|
156
|
+
# For packed encoding, concatenate values directly
|
|
157
|
+
values.map { |value| type(type, value) }.join.b
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The ABI encoder needs to be able to determine between a hex `"123"`
|
|
161
|
+
# and a binary `"123"` string.
|
|
162
|
+
def handle_hex_string(val, len)
|
|
163
|
+
if Util.prefixed?(val) || (len == val.size / 2 && Util.hex?(val))
|
|
164
|
+
# There is no way telling whether a string is hex or binary with certainty
|
|
165
|
+
# in Ruby. Therefore, we assume a `0x` prefix to indicate a hex string.
|
|
166
|
+
# Additionally, if the string size is exactly the double of the expected
|
|
167
|
+
# binary size, we can assume a hex value.
|
|
168
|
+
Util.hex_to_bin(val)
|
|
169
|
+
else
|
|
170
|
+
# Everything else will be assumed binary or raw string.
|
|
171
|
+
val.b
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/tron/abi/util.rb
CHANGED
|
@@ -26,14 +26,15 @@ module Tron
|
|
|
26
26
|
((x + 31) / 32).floor * 32
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Pads an integer to
|
|
29
|
+
# Pads an integer to a specified number of bytes in binary format
|
|
30
30
|
#
|
|
31
31
|
# @param x [Integer] the integer to pad
|
|
32
|
+
# @param len [Integer] the number of bytes to pad to (default: 32)
|
|
32
33
|
# @return [String] the padded integer as a binary string
|
|
33
|
-
def zpad_int(x)
|
|
34
|
+
def zpad_int(x, len = 32)
|
|
34
35
|
# Ensure x is positive for modulo operation
|
|
35
|
-
x = x % (2 **
|
|
36
|
-
[x.to_s(16).rjust(
|
|
36
|
+
x = x % (2 ** (8 * len)) if x >= 2 ** (8 * len) || x < 0
|
|
37
|
+
[x.to_s(16).rjust(len * 2, '0')].pack('H*')
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# Pads a string to a specified length with null bytes
|
data/lib/tron/abi.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Tron
|
|
|
22
22
|
require_relative 'abi/event'
|
|
23
23
|
require_relative 'abi/util'
|
|
24
24
|
require_relative 'abi/constant'
|
|
25
|
+
require_relative 'abi/packed'
|
|
25
26
|
|
|
26
27
|
# For address handling functionality
|
|
27
28
|
require_relative 'utils/address'
|
|
@@ -139,6 +140,21 @@ module Tron
|
|
|
139
140
|
types
|
|
140
141
|
end
|
|
141
142
|
|
|
143
|
+
# Encodes values using Solidity packed encoding (no padding to 32 bytes)
|
|
144
|
+
#
|
|
145
|
+
# @param types [Array<String>] array of type strings
|
|
146
|
+
# @param values [Array] array of values to encode
|
|
147
|
+
# @return [String] binary string of packed encoded values
|
|
148
|
+
def self.solidity_packed(types, values)
|
|
149
|
+
raise ArgumentError, "Types and values must be the same length" if types.length != values.length
|
|
150
|
+
|
|
151
|
+
packed = types.zip(values).map do |type, value|
|
|
152
|
+
Tron::Abi::Packed::Encoder.type(type, value)
|
|
153
|
+
end.join
|
|
154
|
+
|
|
155
|
+
packed.force_encoding(Encoding::ASCII_8BIT)
|
|
156
|
+
end
|
|
157
|
+
|
|
142
158
|
# Decode output from a contract call
|
|
143
159
|
#
|
|
144
160
|
# @param type_str [String] the output type
|
data/lib/tron/key.rb
CHANGED
|
@@ -146,6 +146,32 @@ module Tron
|
|
|
146
146
|
sign(hashed_message)
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
+
# Signs a typed data hash using TRON TIP-191 standard
|
|
150
|
+
# This is used for structured data signing (similar to EIP-712 but with TIP-191 prefix)
|
|
151
|
+
# The hash should be the keccak256 of the packed structured data
|
|
152
|
+
#
|
|
153
|
+
# @param hash [String] binary hash (32 bytes) of the structured data
|
|
154
|
+
# @return [String] signature as hexadecimal string with v value adjusted to 27/28
|
|
155
|
+
def sign_typed_data(hash)
|
|
156
|
+
# Add TRON TIP-191 prefix for 32-byte hashes
|
|
157
|
+
# Ref: https://github.com/tronprotocol/tips/blob/master/tip-191.md
|
|
158
|
+
prefix = "\x19Tron Signed Message:\n32"
|
|
159
|
+
prefixed_data = prefix.b + hash
|
|
160
|
+
prefixed_hash = Tron::Utils::Crypto.keccak256(prefixed_data)
|
|
161
|
+
|
|
162
|
+
# Sign the prefixed hash
|
|
163
|
+
context = Secp256k1::Context.new
|
|
164
|
+
compact, recovery_id = context.sign_recoverable(@private_key, prefixed_hash).compact
|
|
165
|
+
signature = compact.bytes
|
|
166
|
+
|
|
167
|
+
# Adjust v value to 27 or 28 (standard Ethereum/TRON signature format)
|
|
168
|
+
# Instead of recovery_id (0 or 1), use 27 + recovery_id
|
|
169
|
+
v = recovery_id + 27
|
|
170
|
+
signature << v
|
|
171
|
+
|
|
172
|
+
Tron::Utils::Crypto.bin_to_hex(signature.pack('c*'))
|
|
173
|
+
end
|
|
174
|
+
|
|
149
175
|
# Verifies a signature against a data blob
|
|
150
176
|
#
|
|
151
177
|
# @param blob [String] the original signed data
|
|
@@ -41,6 +41,144 @@ module Tron
|
|
|
41
41
|
Utils::HTTP.post(endpoint, payload)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# Gets detailed transaction information by transaction ID (includes error details)
|
|
45
|
+
#
|
|
46
|
+
# @param tx_id [String] the transaction ID (txID)
|
|
47
|
+
# @return [Hash] the detailed transaction information from the blockchain
|
|
48
|
+
def get_transaction_info(tx_id)
|
|
49
|
+
endpoint = "#{@base_url}/wallet/gettransactioninfobyid"
|
|
50
|
+
payload = { value: tx_id }
|
|
51
|
+
|
|
52
|
+
Utils::HTTP.post(endpoint, payload)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Waits for transaction confirmation
|
|
56
|
+
#
|
|
57
|
+
# @param tx_id [String] the transaction ID to wait for
|
|
58
|
+
# @param max_attempts [Integer] maximum number of attempts to check (default: 15)
|
|
59
|
+
# @return [Hash] the transaction information when confirmed
|
|
60
|
+
# @raise [RuntimeError] if transaction is reverted or times out
|
|
61
|
+
def wait_for_transaction(tx_id, max_attempts = 15)
|
|
62
|
+
print "Waiting for transaction: #{tx_id}"
|
|
63
|
+
|
|
64
|
+
max_attempts.times do |i|
|
|
65
|
+
sleep 2
|
|
66
|
+
print '.'
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
tx_info = get_transaction(tx_id)
|
|
70
|
+
|
|
71
|
+
if tx_info && tx_info['ret'] && tx_info['ret'][0]
|
|
72
|
+
result = tx_info['ret'][0]['contractRet']
|
|
73
|
+
|
|
74
|
+
if result == 'SUCCESS'
|
|
75
|
+
puts "\nā Transaction confirmed"
|
|
76
|
+
return tx_info
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if result == 'REVERT'
|
|
80
|
+
puts "\nā Transaction reverted"
|
|
81
|
+
|
|
82
|
+
# Get the actual revert reason from transaction info
|
|
83
|
+
error_message = extract_revert_reason(tx_id)
|
|
84
|
+
|
|
85
|
+
# Raise with just the error message
|
|
86
|
+
raise "Transaction failed: #{error_message}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
rescue RuntimeError => e
|
|
90
|
+
# If it's already a formatted error, re-raise it
|
|
91
|
+
raise e if e.message.start_with?('Transaction failed:')
|
|
92
|
+
# Transaction might not be available yet, continue waiting
|
|
93
|
+
next if i < max_attempts - 1
|
|
94
|
+
raise e
|
|
95
|
+
rescue => e
|
|
96
|
+
# Transaction might not be available yet, continue waiting
|
|
97
|
+
next if i < max_attempts - 1
|
|
98
|
+
raise e
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
raise 'Transaction timeout'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
# Extracts the revert reason from a failed transaction
|
|
108
|
+
#
|
|
109
|
+
# @param tx_id [String] the transaction ID
|
|
110
|
+
# @return [String] the decoded error message
|
|
111
|
+
def extract_revert_reason(tx_id)
|
|
112
|
+
tx_detail = get_transaction_info(tx_id)
|
|
113
|
+
|
|
114
|
+
# Debug: Print the full transaction info
|
|
115
|
+
if ENV['DEBUG']
|
|
116
|
+
puts "\nš DEBUG - Full transaction info:"
|
|
117
|
+
puts JSON.pretty_generate(tx_detail) rescue puts(tx_detail.inspect)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Try to get error from contractResult (this contains the ABI-encoded revert reason)
|
|
121
|
+
if tx_detail && tx_detail['contractResult'] && !tx_detail['contractResult'].empty?
|
|
122
|
+
contract_result = tx_detail['contractResult'][0]
|
|
123
|
+
puts "š DEBUG - contractResult: #{contract_result}" if ENV['DEBUG']
|
|
124
|
+
|
|
125
|
+
# Check if this is an Error(string) revert (starts with 08c379a0)
|
|
126
|
+
if contract_result && contract_result.start_with?('08c379a0')
|
|
127
|
+
begin
|
|
128
|
+
# ABI encoding for Error(string):
|
|
129
|
+
# - 4 bytes (8 hex chars): function selector (08c379a0)
|
|
130
|
+
# - 32 bytes (64 hex chars): offset to string data
|
|
131
|
+
# - 32 bytes (64 hex chars): length of string
|
|
132
|
+
# - N bytes: actual string data
|
|
133
|
+
|
|
134
|
+
# Skip selector (8 chars) and offset (64 chars)
|
|
135
|
+
data = contract_result[8 + 64..-1]
|
|
136
|
+
|
|
137
|
+
# Read length (next 64 chars)
|
|
138
|
+
length_hex = data[0...64]
|
|
139
|
+
length = length_hex.to_i(16)
|
|
140
|
+
puts "š DEBUG - String length: #{length}" if ENV['DEBUG']
|
|
141
|
+
|
|
142
|
+
# Read string data (next length * 2 hex chars)
|
|
143
|
+
string_hex = data[64, length * 2]
|
|
144
|
+
puts "š DEBUG - String hex: #{string_hex}" if ENV['DEBUG']
|
|
145
|
+
|
|
146
|
+
# Decode hex to UTF-8
|
|
147
|
+
error_message = [string_hex].pack('H*').force_encoding('UTF-8')
|
|
148
|
+
puts "š DEBUG - Decoded error: #{error_message}" if ENV['DEBUG']
|
|
149
|
+
|
|
150
|
+
return error_message unless error_message.empty?
|
|
151
|
+
rescue => e
|
|
152
|
+
puts "š DEBUG - Error decoding contractResult: #{e.message}" if ENV['DEBUG']
|
|
153
|
+
puts "š DEBUG - Backtrace: #{e.backtrace.first(3).join("\n")}" if ENV['DEBUG']
|
|
154
|
+
# If decoding fails, fall through
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Try to get error from resMessage (hex-encoded error string)
|
|
160
|
+
if tx_detail && tx_detail['resMessage']
|
|
161
|
+
error_hex = tx_detail['resMessage']
|
|
162
|
+
puts "š DEBUG - resMessage (hex): #{error_hex}" if ENV['DEBUG']
|
|
163
|
+
|
|
164
|
+
begin
|
|
165
|
+
# Decode hex to UTF-8 string
|
|
166
|
+
error_message = [error_hex].pack('H*').force_encoding('UTF-8')
|
|
167
|
+
# Clean up the message (remove null bytes and control characters)
|
|
168
|
+
error_message = error_message.gsub(/[\x00-\x1f\x7f]/, '').strip
|
|
169
|
+
|
|
170
|
+
puts "š DEBUG - Decoded resMessage: #{error_message}" if ENV['DEBUG']
|
|
171
|
+
return error_message unless error_message.empty?
|
|
172
|
+
rescue => e
|
|
173
|
+
puts "š DEBUG - Error decoding resMessage: #{e.message}" if ENV['DEBUG']
|
|
174
|
+
# If decoding fails, continue to try other methods
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Default to generic revert message
|
|
179
|
+
'REVERT opcode executed'
|
|
180
|
+
end
|
|
181
|
+
|
|
44
182
|
private
|
|
45
183
|
|
|
46
184
|
# Signs a transaction locally using the private key
|
|
@@ -104,8 +242,26 @@ module Tron
|
|
|
104
242
|
|
|
105
243
|
# Check if the transaction was successful
|
|
106
244
|
unless response['result']
|
|
107
|
-
|
|
108
|
-
|
|
245
|
+
error_message = response['Error'] || response['error'] || response['message'] || 'Unknown error'
|
|
246
|
+
|
|
247
|
+
# Build detailed error message
|
|
248
|
+
error_details = ["ā Transaction broadcast failed: #{error_message}"]
|
|
249
|
+
|
|
250
|
+
# Add code if available
|
|
251
|
+
if response['code']
|
|
252
|
+
error_details << " Error code: #{response['code']}"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Add transaction ID if available
|
|
256
|
+
if response['txid'] || response['txID']
|
|
257
|
+
tx_id = response['txid'] || response['txID']
|
|
258
|
+
error_details << " Transaction ID: #{tx_id}"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Add full response for debugging
|
|
262
|
+
error_details << " Full response: #{response.inspect}"
|
|
263
|
+
|
|
264
|
+
raise error_details.join("\n")
|
|
109
265
|
end
|
|
110
266
|
|
|
111
267
|
response
|
data/lib/tron/version.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.
|
|
4
|
+
version: 1.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bolo Michelin
|
|
@@ -141,6 +141,8 @@ files:
|
|
|
141
141
|
- lib/tron/abi/encoder.rb
|
|
142
142
|
- lib/tron/abi/event.rb
|
|
143
143
|
- lib/tron/abi/function.rb
|
|
144
|
+
- lib/tron/abi/packed.rb
|
|
145
|
+
- lib/tron/abi/packed/encoder.rb
|
|
144
146
|
- lib/tron/abi/type.rb
|
|
145
147
|
- lib/tron/abi/util.rb
|
|
146
148
|
- lib/tron/cache.rb
|