mixin_bot 1.4.0 → 2.0.0

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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +75 -0
  3. data/API_COVERAGE.md +143 -0
  4. data/CHANGELOG.md +112 -0
  5. data/README.md +375 -0
  6. data/docs/agent/cli.md +149 -0
  7. data/docs/agent/cookbook.md +152 -0
  8. data/examples/blaze.rb +43 -0
  9. data/examples/config.yml.example +21 -0
  10. data/lib/mixin_bot/address.rb +43 -3
  11. data/lib/mixin_bot/api/app.rb +7 -0
  12. data/lib/mixin_bot/api/asset.rb +114 -3
  13. data/lib/mixin_bot/api/auth.rb +19 -10
  14. data/lib/mixin_bot/api/blaze.rb +81 -0
  15. data/lib/mixin_bot/api/chain.rb +94 -0
  16. data/lib/mixin_bot/api/code.rb +16 -0
  17. data/lib/mixin_bot/api/computer_api.rb +60 -0
  18. data/lib/mixin_bot/api/conversation.rb +7 -1
  19. data/lib/mixin_bot/api/deposit.rb +12 -0
  20. data/lib/mixin_bot/api/encrypted_message.rb +1 -1
  21. data/lib/mixin_bot/api/fiat.rb +12 -0
  22. data/lib/mixin_bot/api/inscription.rb +2 -2
  23. data/lib/mixin_bot/api/legacy_collectible.rb +26 -27
  24. data/lib/mixin_bot/api/legacy_multisig.rb +20 -21
  25. data/lib/mixin_bot/api/legacy_output.rb +10 -3
  26. data/lib/mixin_bot/api/legacy_payment.rb +2 -0
  27. data/lib/mixin_bot/api/legacy_snapshot.rb +16 -0
  28. data/lib/mixin_bot/api/legacy_transaction.rb +28 -13
  29. data/lib/mixin_bot/api/legacy_transfer.rb +11 -8
  30. data/lib/mixin_bot/api/legacy_user.rb +51 -0
  31. data/lib/mixin_bot/api/me.rb +99 -3
  32. data/lib/mixin_bot/api/message.rb +18 -27
  33. data/lib/mixin_bot/api/multisig.rb +19 -0
  34. data/lib/mixin_bot/api/network.rb +17 -0
  35. data/lib/mixin_bot/api/network_asset.rb +27 -0
  36. data/lib/mixin_bot/api/output.rb +1 -1
  37. data/lib/mixin_bot/api/pin.rb +16 -3
  38. data/lib/mixin_bot/api/pin_payload.rb +26 -0
  39. data/lib/mixin_bot/api/session.rb +14 -0
  40. data/lib/mixin_bot/api/snapshot.rb +6 -0
  41. data/lib/mixin_bot/api/tip.rb +74 -1
  42. data/lib/mixin_bot/api/transaction.rb +106 -17
  43. data/lib/mixin_bot/api/transfer.rb +141 -14
  44. data/lib/mixin_bot/api/turn.rb +12 -0
  45. data/lib/mixin_bot/api/user.rb +148 -45
  46. data/lib/mixin_bot/api/withdraw.rb +24 -23
  47. data/lib/mixin_bot/api.rb +248 -3
  48. data/lib/mixin_bot/bot_auth.rb +71 -0
  49. data/lib/mixin_bot/cli/api.rb +224 -143
  50. data/lib/mixin_bot/cli/base.rb +77 -0
  51. data/lib/mixin_bot/cli/call.rb +71 -0
  52. data/lib/mixin_bot/cli/errors.rb +56 -0
  53. data/lib/mixin_bot/cli/node.rb +9 -2
  54. data/lib/mixin_bot/cli/output.rb +196 -0
  55. data/lib/mixin_bot/cli/schema.rb +274 -0
  56. data/lib/mixin_bot/cli/schema_command.rb +21 -0
  57. data/lib/mixin_bot/cli/utils.rb +114 -18
  58. data/lib/mixin_bot/cli.rb +124 -48
  59. data/lib/mixin_bot/client/error_mapper.rb +40 -0
  60. data/lib/mixin_bot/client.rb +94 -64
  61. data/lib/mixin_bot/computer.rb +132 -0
  62. data/lib/mixin_bot/configuration.rb +108 -1
  63. data/lib/mixin_bot/errors.rb +102 -0
  64. data/lib/mixin_bot/models/address.rb +11 -0
  65. data/lib/mixin_bot/models/api_envelope.rb +67 -0
  66. data/lib/mixin_bot/models/asset.rb +11 -0
  67. data/lib/mixin_bot/models/ghost_keys.rb +14 -0
  68. data/lib/mixin_bot/models/output.rb +11 -0
  69. data/lib/mixin_bot/models/safe_multisig_request.rb +11 -0
  70. data/lib/mixin_bot/models/sequencer_transaction_request.rb +11 -0
  71. data/lib/mixin_bot/models/user.rb +11 -0
  72. data/lib/mixin_bot/models.rb +10 -0
  73. data/lib/mixin_bot/monitor.rb +77 -0
  74. data/lib/mixin_bot/transaction/buffer.rb +34 -0
  75. data/lib/mixin_bot/transaction/decoder.rb +227 -0
  76. data/lib/mixin_bot/transaction/encoder.rb +255 -0
  77. data/lib/mixin_bot/transaction.rb +6 -475
  78. data/lib/mixin_bot/url_scheme.rb +63 -0
  79. data/lib/mixin_bot/utils/address.rb +17 -80
  80. data/lib/mixin_bot/utils/crypto.rb +173 -1
  81. data/lib/mixin_bot/utils/decoder.rb +1 -1
  82. data/lib/mixin_bot/utils/encoder.rb +13 -0
  83. data/lib/mixin_bot/utils.rb +45 -0
  84. data/lib/mixin_bot/uuid.rb +78 -1
  85. data/lib/mixin_bot/version.rb +11 -1
  86. data/lib/mixin_bot.rb +172 -18
  87. data/lib/mvm/bridge.rb +46 -0
  88. data/lib/mvm/client.rb +60 -0
  89. data/lib/mvm/nft.rb +4 -2
  90. data/lib/mvm/registry.rb +2 -1
  91. data/lib/mvm.rb +93 -0
  92. data/lib/tasks/api_coverage.rake +20 -0
  93. data/llms.txt +29 -0
  94. metadata +77 -9
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class Transaction
5
+ ##
6
+ # Parses raw transaction bytes into a {Transaction} instance.
7
+ #
8
+ class Decoder
9
+ def initialize(transaction)
10
+ @tx = transaction
11
+ end
12
+
13
+ def decode
14
+ raw = [@tx.hex].pack('H*').bytes
15
+ @tx.hash = SHA3::Digest::SHA256.hexdigest(raw.pack('C*'))
16
+ @buf = Buffer.new(raw)
17
+
18
+ magic = @buf.shift(2)
19
+ raise ArgumentError, 'Not valid raw' unless magic == Transaction::MAGIC
20
+
21
+ _version = @buf.shift(2)
22
+ @tx.version = MixinBot.utils.decode_int _version
23
+
24
+ asset = @buf.shift(32)
25
+ @tx.asset = asset.pack('C*').unpack1('H*')
26
+
27
+ decode_inputs
28
+ decode_outputs
29
+ decode_references if @tx.version >= Transaction::REFERENCES_TX_VERSION && @tx.references.present?
30
+
31
+ extra_size = MixinBot.utils.decode_uint32 @buf.shift(4)
32
+ @tx.extra = @buf.shift(extra_size).pack('C*')
33
+
34
+ num = MixinBot.utils.decode_uint16 @buf.shift(2)
35
+ if num == Transaction::MAX_ENCODE_INT
36
+ @tx.aggregated = {}
37
+
38
+ prefix = MixinBot.utils.decode_uint16(@buf.shift(2))
39
+ raise ArgumentError, 'invalid aggregated' unless prefix == Transaction::AGGREGATED_SIGNATURE_PREFIX
40
+
41
+ @tx.aggregated['signature'] = @buf.shift(64).pack('C*').unpack1('H*')
42
+
43
+ byte = @buf.shift
44
+ case byte
45
+ when Transaction::AGGREGATED_SIGNATURE_ORDINAY_MASK.first
46
+ @tx.aggregated['signers'] = []
47
+ masks_size = MixinBot.utils.decode_uint16 @buf.shift(2)
48
+ masks = @buf.shift(masks_size)
49
+ masks = Array(masks)
50
+
51
+ masks.each_with_index do |mask, i|
52
+ 8.times do |j|
53
+ k = 1 << j
54
+ @tx.aggregated['signers'].push((i * 8) + j) if mask & k == k
55
+ end
56
+ end
57
+ when Transaction::AGGREGATED_SIGNATURE_SPARSE_MASK.first
58
+ signers_size = MixinBot.utils.decode_uint16 @buf.shift(2)
59
+ @tx.aggregated['signers'] = []
60
+ unless signers_size.zero?
61
+ signers_size.times do
62
+ @tx.aggregated['signers'].push MixinBot.utils.decode_uint16(@buf.shift(2))
63
+ end
64
+ end
65
+ end
66
+ elsif num.present? && num.positive? && @buf.size.positive?
67
+ @tx.signatures = []
68
+ num.times do
69
+ signature = {}
70
+
71
+ keys_size = MixinBot.utils.decode_uint16 @buf.shift(2)
72
+
73
+ keys_size.times do
74
+ index = MixinBot.utils.decode_uint16 @buf.shift(2)
75
+ signature[index] = @buf.shift(64).pack('C*').unpack1('H*')
76
+ end
77
+
78
+ @tx.signatures << signature
79
+ end
80
+ end
81
+
82
+ @tx
83
+ end
84
+
85
+ private
86
+
87
+ def decode_inputs
88
+ inputs_size = MixinBot.utils.decode_uint16 @buf.shift(2)
89
+ @tx.inputs = []
90
+ inputs_size.times do
91
+ input = {}
92
+ hash = @buf.shift(32)
93
+ input['hash'] = hash.pack('C*').unpack1('H*')
94
+
95
+ index = @buf.shift(2)
96
+ input['index'] = MixinBot.utils.decode_uint16 index
97
+
98
+ if @buf.peek(2) == Transaction::NULL_BYTES
99
+ @buf.shift(2)
100
+ else
101
+ genesis_size = MixinBot.utils.decode_uint16 @buf.shift(2)
102
+ genesis = @buf.shift genesis_size
103
+ input['genesis'] = genesis.pack('C*').unpack1('H*')
104
+ end
105
+
106
+ if @buf.peek(2) == Transaction::NULL_BYTES
107
+ @buf.shift(2)
108
+ else
109
+ magic = @buf.shift(2)
110
+ raise ArgumentError, 'Not valid input' unless magic == Transaction::MAGIC
111
+
112
+ deposit = {}
113
+ deposit['chain'] = @buf.shift(32).pack('C*').unpack1('H*')
114
+
115
+ asset_size = MixinBot.utils.decode_uint16 @buf.shift(2)
116
+ deposit['asset'] = @buf.shift(asset_size).unpack1('H*')
117
+
118
+ transaction_size = MixinBot.utils.decode_uint16 @buf.shift(2)
119
+ deposit['transaction'] = @buf.shift(transaction_size).unpack1('H*')
120
+
121
+ deposit['index'] = MixinBot.utils.decode_uint64 @buf.shift(8)
122
+
123
+ amount_size = MixinBot.utils.decode_uint16 @buf.shift(2)
124
+ deposit['amount'] = MixinBot.utils.decode_int @buf.shift(amount_size)
125
+
126
+ input['deposit'] = deposit
127
+ end
128
+
129
+ if @buf.peek(2) == Transaction::NULL_BYTES
130
+ @buf.shift(2)
131
+ else
132
+ magic = @buf.shift(2)
133
+ raise ArgumentError, 'Not valid input' unless magic == Transaction::MAGIC
134
+
135
+ mint = {}
136
+ if @buf.peek(2) == Transaction::NULL_BYTES
137
+ @buf.shift(2)
138
+ else
139
+ group_size = MixinBot.utils.decode_uint16 @buf.shift(2)
140
+ mint['group'] = @buf.shift(group_size).unpack1('H*')
141
+ end
142
+
143
+ mint['batch'] = MixinBot.utils.decode_uint64 @buf.shift(8)
144
+ _amount_size = MixinBot.utils.decode_uint16 @buf.shift(2)
145
+ mint['amount'] = MixinBot.utils.decode_int @buf.shift(_amount_size)
146
+
147
+ input['mint'] = mint
148
+ end
149
+
150
+ @tx.inputs.push input
151
+ end
152
+
153
+ @tx
154
+ end
155
+
156
+ def decode_outputs
157
+ outputs_size = MixinBot.utils.decode_uint16 @buf.shift(2)
158
+ @tx.outputs = []
159
+ outputs_size.times do
160
+ output = {}
161
+
162
+ @buf.shift
163
+ type = @buf.shift
164
+ output['type'] = type
165
+
166
+ amount_size = MixinBot.utils.decode_uint16 @buf.shift(2)
167
+ output['amount'] = format('%.8f', MixinBot.utils.decode_int(@buf.shift(amount_size)).to_f / 1e8).gsub(/\.?0+$/, '')
168
+
169
+ output['keys'] = []
170
+ keys_size = MixinBot.utils.decode_uint16 @buf.shift(2)
171
+ keys_size.times do
172
+ output['keys'].push @buf.shift(32).pack('C*').unpack1('H*')
173
+ end
174
+
175
+ output['mask'] = @buf.shift(32).pack('C*').unpack1('H*')
176
+
177
+ script_size = MixinBot.utils.decode_uint16 @buf.shift(2)
178
+ output['script'] = @buf.shift(script_size).pack('C*').unpack1('H*')
179
+
180
+ if @buf.peek(2) == Transaction::NULL_BYTES
181
+ @buf.shift(2)
182
+ else
183
+ magic = @buf.shift(2)
184
+ raise ArgumentError, 'Not valid output' unless magic == Transaction::MAGIC
185
+
186
+ withdrawal = {}
187
+ withdrawal['chain'] = @buf.shift(32).pack('C*').unpack1('H*')
188
+
189
+ asset_size = MixinBot.utils.decode_uint16 @buf.shift(2)
190
+ withdrawal['asset'] = @buf.shift(asset_size).unpack1('H*')
191
+
192
+ if @buf.peek(2) == Transaction::NULL_BYTES
193
+ @buf.shift(2)
194
+ else
195
+ address_size = MixinBot.utils.decode_uint16 @buf.shift(2)
196
+ withdrawal['address'] = @buf.shift(address_size).pack('C*').unpack1('H*')
197
+ end
198
+
199
+ if @buf.peek(2) == Transaction::NULL_BYTES
200
+ @buf.shift(2)
201
+ else
202
+ tag_size = MixinBot.utils.decode_uint16 @buf.shift(2)
203
+ withdrawal['tag'] = @buf.shift(tag_size).pack('C*').unpack1('H*')
204
+ end
205
+
206
+ output['withdrawal'] = withdrawal
207
+ end
208
+
209
+ @tx.outputs.push output
210
+ end
211
+
212
+ @tx
213
+ end
214
+
215
+ def decode_references
216
+ references_size = MixinBot.utils.decode_uint16 @buf.shift(2)
217
+ @tx.references = []
218
+
219
+ references_size.times do
220
+ @tx.references.push @buf.shift(32).pack('C*').unpack1('H*')
221
+ end
222
+
223
+ @tx
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class Transaction
5
+ ##
6
+ # Builds the binary encoding for a {Transaction} instance.
7
+ #
8
+ class Encoder
9
+ def initialize(transaction)
10
+ @tx = transaction
11
+ end
12
+
13
+ def encode
14
+ raise InvalidTransactionFormatError, 'asset is required' if @tx.asset.blank?
15
+ raise InvalidTransactionFormatError, 'inputs is required' if @tx.inputs.blank?
16
+ raise InvalidTransactionFormatError, 'outputs is required' if @tx.outputs.blank?
17
+
18
+ bytes = []
19
+
20
+ bytes += Transaction::MAGIC
21
+ bytes += [0, @tx.version]
22
+ bytes += [@tx.asset].pack('H*').bytes
23
+
24
+ bytes += encode_inputs
25
+ bytes += encode_outputs
26
+ bytes += encode_references if @tx.version >= Transaction::REFERENCES_TX_VERSION && @tx.references.present?
27
+
28
+ extra_bytes = @tx.extra.bytes
29
+ raise InvalidTransactionFormatError, 'extra is too long' if extra_bytes.size > Transaction::MAX_EXTRA_SIZE
30
+
31
+ bytes += MixinBot.utils.encode_uint32 extra_bytes.size
32
+ bytes += extra_bytes
33
+
34
+ bytes += if @tx.aggregated.nil?
35
+ encode_signatures
36
+ else
37
+ encode_aggregated_signature
38
+ end
39
+
40
+ @tx.hash = SHA3::Digest::SHA256.hexdigest bytes.pack('C*')
41
+ @tx.hex = bytes.pack('C*').unpack1('H*')
42
+
43
+ @tx
44
+ end
45
+
46
+ private
47
+
48
+ def encode_inputs
49
+ bytes = []
50
+
51
+ bytes += MixinBot.utils.encode_uint16(@tx.inputs.size)
52
+
53
+ @tx.inputs.each do |input|
54
+ bytes += [input['hash']].pack('H*').bytes
55
+ bytes += MixinBot.utils.encode_uint16(input['index'])
56
+
57
+ genesis = input['genesis'] || ''
58
+ if genesis.empty?
59
+ bytes += Transaction::NULL_BYTES
60
+ else
61
+ genesis_bytes = [genesis].pack('H*').bytes
62
+ bytes += MixinBot.utils.encode_uint16 genesis_bytes.size
63
+ bytes += genesis_bytes
64
+ end
65
+
66
+ deposit = input['deposit']
67
+ if deposit.nil?
68
+ bytes += Transaction::NULL_BYTES
69
+ else
70
+ bytes += Transaction::MAGIC
71
+ bytes += [deposit['chain']].pack('H*').bytes
72
+
73
+ asset_bytes = [deposit['asset']].pack('H*')
74
+ bytes += MixinBot.utils.encode_uint16 asset_bytes.size
75
+ bytes += asset_bytes.bytes
76
+
77
+ transaction_bytes = [deposit['transaction']].pack('H*')
78
+ bytes += MixinBot.utils.encode_uint16 transaction_bytes.size
79
+ bytes += transaction_bytes.bytes
80
+
81
+ bytes += MixinBot.utils.encode_uint64 deposit['index']
82
+
83
+ amount_bytes = MixinBot.utils.bytes_of deposit['amount']
84
+ bytes += MixinBot.utils.encode_uint16 amount_bytes.size
85
+ bytes += amount_bytes
86
+ end
87
+
88
+ mint = input['mint']
89
+ if mint.nil?
90
+ bytes += Transaction::NULL_BYTES
91
+ else
92
+ bytes += Transaction::MAGIC
93
+
94
+ group = mint['group'] || ''
95
+ if group.empty?
96
+ bytes += Transaction::NULL_BYTES
97
+ else
98
+ group_bytes = [group].pack('H*')
99
+ bytes += MixinBot.utils.encode_uint16 group_bytes.size
100
+ bytes += group_bytes.bytes
101
+ end
102
+
103
+ bytes += MixinBot.utils.encode_uint64 mint['batch']
104
+
105
+ amount_bytes = MixinBot.utils.encode_int mint['amount']
106
+ bytes += MixinBot.utils.encode_uint16 amount_bytes.size
107
+ bytes += amount_bytes
108
+ end
109
+ end
110
+
111
+ bytes
112
+ end
113
+
114
+ def encode_outputs
115
+ bytes = []
116
+
117
+ bytes += MixinBot.utils.encode_uint16 @tx.outputs.size
118
+
119
+ @tx.outputs.each do |output|
120
+ type = output['type'] || 0
121
+ bytes += [0x00, type]
122
+
123
+ amount_bytes = MixinBot.utils.encode_int((output['amount'].to_d * 1e8).round)
124
+ bytes += MixinBot.utils.encode_uint16 amount_bytes.size
125
+ bytes += amount_bytes
126
+
127
+ bytes += MixinBot.utils.encode_uint16 output['keys'].size
128
+ output['keys'].each do |key|
129
+ bytes += [key].pack('H*').bytes
130
+ end
131
+
132
+ bytes += [output['mask']].pack('H*').bytes
133
+
134
+ script_bytes = [output['script']].pack('H*').bytes
135
+ bytes += MixinBot.utils.encode_uint16 script_bytes.size
136
+ bytes += script_bytes
137
+
138
+ withdrawal = output['withdrawal']
139
+ if withdrawal.nil?
140
+ bytes += Transaction::NULL_BYTES
141
+ else
142
+ bytes += Transaction::MAGIC
143
+
144
+ bytes += [withdrawal['chain']].pack('H*').bytes
145
+
146
+ asset_bytes = [withdrawal['asset']].pack('H*')
147
+ bytes += MixinBot.utils.encode_uint16 asset_bytes.bytesize
148
+ bytes += asset_bytes.bytes
149
+
150
+ address = withdrawal['address'] || ''
151
+ if address.empty?
152
+ bytes += Transaction::NULL_BYTES
153
+ else
154
+ address_bytes = [address].pack('H*').bytes
155
+ bytes += MixinBot.utils.encode_uint16 address_bytes.size
156
+ bytes += address_bytes
157
+ end
158
+
159
+ tag = withdrawal['tag'] || ''
160
+ if tag.empty?
161
+ bytes += Transaction::NULL_BYTES
162
+ else
163
+ tag_bytes = [tag].pack('H*').bytes
164
+ bytes += MixinBot.utils.encode_uint16 tag_bytes.size
165
+ bytes += tag_bytes
166
+ end
167
+ end
168
+ end
169
+
170
+ bytes
171
+ end
172
+
173
+ def encode_references
174
+ bytes = []
175
+
176
+ bytes += MixinBot.utils.encode_uint16 @tx.references.size
177
+
178
+ @tx.references.each do |reference|
179
+ bytes += [reference].pack('H*').bytes
180
+ end
181
+
182
+ bytes
183
+ end
184
+
185
+ def encode_aggregated_signature
186
+ bytes = []
187
+
188
+ bytes += MixinBot.utils.encode_uint16 Transaction::MAX_ENCODE_INT
189
+ bytes += MixinBot.utils.encode_uint16 Transaction::AGGREGATED_SIGNATURE_PREFIX
190
+ bytes += [@tx.aggregated['signature']].pack('H*').bytes
191
+
192
+ signers = @tx.aggregated['signers'] || []
193
+ if signers.empty?
194
+ bytes += Transaction::AGGREGATED_SIGNATURE_ORDINAY_MASK
195
+ bytes += Transaction::NULL_BYTES
196
+ return bytes
197
+ end
198
+
199
+ signers.each_with_index do |sig, i|
200
+ raise ArgumentError, 'signers not sorted' if i.positive? && sig <= signers[i - 1]
201
+ raise ArgumentError, 'signers not sorted' if sig > Transaction::MAX_ENCODE_INT
202
+ end
203
+
204
+ max = signers.last
205
+ sig_byte_len = [@tx.aggregated['signature']].pack('H*').bytes.size
206
+ if ((max / 8) + 1) > sig_byte_len
207
+ bytes += Transaction::AGGREGATED_SIGNATURE_SPARSE_MASK
208
+ bytes += MixinBot.utils.encode_uint16 signers.size
209
+ signers.each { |signer| bytes += MixinBot.utils.encode_uint16(signer) }
210
+ else
211
+ masks_bytes = Array.new((max / 8) + 1, 0)
212
+ signers.each do |signer|
213
+ masks_bytes[signer / 8] ^= (1 << (signer % 8))
214
+ end
215
+ bytes += Transaction::AGGREGATED_SIGNATURE_ORDINAY_MASK
216
+ bytes += MixinBot.utils.encode_uint16 masks_bytes.size
217
+ bytes += masks_bytes
218
+ end
219
+
220
+ bytes
221
+ end
222
+
223
+ def encode_signatures
224
+ bytes = []
225
+
226
+ sl =
227
+ if @tx.signatures.is_a? Array
228
+ @tx.signatures.size
229
+ else
230
+ 0
231
+ end
232
+
233
+ raise ArgumentError, 'signatures overflow' if sl == Transaction::MAX_ENCODE_INT
234
+
235
+ bytes += MixinBot.utils.encode_uint16 sl
236
+
237
+ if sl.positive?
238
+ @tx.signatures.each do |signature|
239
+ bytes += MixinBot.utils.encode_uint16 signature.keys.size
240
+
241
+ signature.keys.sort.each do |key|
242
+ signature_bytes = [signature[key]].pack('H*').bytes
243
+ raise ArgumentError, 'Signature should be 64 bytes' if signature_bytes.size != 64
244
+
245
+ bytes += MixinBot.utils.encode_uint16 key
246
+ bytes += signature_bytes
247
+ end
248
+ end
249
+ end
250
+
251
+ bytes
252
+ end
253
+ end
254
+ end
255
+ end