mixin_bot 2.3.0 → 2.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 419e39522e8235c79d12c0506cb4529d34ed130170acc3805fd5827eb63e7428
4
- data.tar.gz: f47598cf481cd07be7ca023c329018320a63b0b111a5ba4c68b646c8bfa2819d
3
+ metadata.gz: 06d699c83730cba7bfd93dedbc47f96b1d97c1cd2d4816babd0d310effce3f99
4
+ data.tar.gz: 8ac62f6fbd15d257d3fae717eb3e3ae1fbca0a46caf9f7f6a03b81797ec7fdf7
5
5
  SHA512:
6
- metadata.gz: 20330faef09d018e2c1e914a3766270648c681d79d633fefd633b6880ff40542f9b730df77ff267974507d25ebdc107bfd5005cacc15f5f0c3968c3cd4cf824f
7
- data.tar.gz: db4cfbd8fd5520d076927326316ebf07682c7065597478d8892e8f94c8a13f72a17d6e3f603f1770f6a66a61fb2c1cf17562060f6c4b9f596c0b67de1baa5031
6
+ metadata.gz: ded5aea75050e8ea99a1fd95de4473229fd0dc8c38fa819b01f039ed1917b791e4ccb8183e519521973447b82f909a333f467b438aacd1a9e50f8dff679ea85d
7
+ data.tar.gz: e4041c6a976a0127cb4d2740cda7315f5c16d72cd6edc0b0ca54f978fac8ce64d33d464e730863181473d361b1f5b69d98ae0903e746f5fea0ba979db8d0e354
data/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — MixinBot
2
2
 
3
- Ruby gem (v2.2.1): Mixin Network REST SDK + `mixinbot` CLI. Parity targets: [bot-api-go-client](https://github.com/MixinNetwork/bot-api-go-client), [bot-api-nodejs-client](https://github.com/MixinNetwork/bot-api-nodejs-client).
3
+ Ruby gem (v2.3.0): Mixin Network REST SDK + `mixinbot` CLI. Parity targets: [bot-api-go-client](https://github.com/MixinNetwork/bot-api-go-client), [bot-api-nodejs-client](https://github.com/MixinNetwork/bot-api-nodejs-client).
4
4
 
5
5
  ## Commands
6
6
 
data/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.4.0] - 2026-07-20
11
+
12
+ ### Added
13
+
14
+ - **Blaze WebSocket User-Agent** — the client now sends a `User-Agent` header on Blaze WebSocket connections.
15
+
16
+ ### Changed
17
+
18
+ - **`sha3` dependency** — upgraded from `~> 1.0` to `~> 2.2` (sha3 2.x renames `SHA3::Digest::SHA256` to `SHA3::Digest::SHA3_256`; hash output is unchanged).
19
+
20
+ ### Fixed
21
+
22
+ - **Payment URL formatting** — `safe_pay_url` now formats amounts without scientific notation for very small or large values.
23
+
24
+ ### Performance
25
+
26
+ - **Encoder allocations** — replaced `Array` splat patterns with `Array#concat` in `Transaction::Encoder`, `Nfo`, `InvoiceEntry`, `EncryptedMessage`, and `MVM::Registry` to avoid O(n²) reallocation.
27
+ - **Bytes pack caching** — cached `bytes.pack` results in transaction encoders to reduce duplicate allocations.
28
+
10
29
  ## [2.3.0] - 2026-05-27
11
30
 
12
31
  ### Added
data/README.md CHANGED
@@ -6,7 +6,7 @@ Ruby SDK and CLI for [Mixin Network](https://developers.mixin.one/docs): authent
6
6
 
7
7
  The gem aims for **parity with the official [bot-api-go-client](https://github.com/MixinNetwork/bot-api-go-client)** Go SDK and **[bot-api-nodejs-client](https://github.com/MixinNetwork/bot-api-nodejs-client)** Node SDK. See [API_COVERAGE.md](API_COVERAGE.md) for the full mapping; run `rake mixin_bot:api_coverage` to confirm no gaps are marked missing.
8
8
 
9
- Current gem version: **2.2.1** (see [CHANGELOG.md](CHANGELOG.md) for breaking changes and deprecations).
9
+ Current gem version: **2.3.0** (see [CHANGELOG.md](CHANGELOG.md) for breaking changes and deprecations).
10
10
 
11
11
  ## Requirements
12
12
 
@@ -109,7 +109,7 @@ module MixinBot
109
109
 
110
110
  self.payload = prefix + msg
111
111
 
112
- checksum = SHA3::Digest::SHA256.digest(MIX_ADDRESS_PREFIX + payload)
112
+ checksum = SHA3::Digest::SHA3_256.digest(MIX_ADDRESS_PREFIX + payload)
113
113
  data = payload + checksum[0...4]
114
114
  data = Base58.binary_to_base58 data, :bitcoin
115
115
  self.address = "#{MIX_ADDRESS_PREFIX}#{data}"
@@ -126,10 +126,10 @@ module MixinBot
126
126
  raise ArgumentError, 'invalid address, length invalid' if data.length < 3 + 16 + 4
127
127
 
128
128
  self.payload = data[...-4]
129
- checksum = SHA3::Digest::SHA256.digest(MIX_ADDRESS_PREFIX + payload)[0...4]
129
+ checksum = SHA3::Digest::SHA3_256.digest(MIX_ADDRESS_PREFIX + payload)[0...4]
130
130
  raise ArgumentError, 'invalid address, checksum invalid' unless checksum == data[-4..]
131
131
  else
132
- checksum = SHA3::Digest::SHA256.digest(MIX_ADDRESS_PREFIX + payload)[0...4]
132
+ checksum = SHA3::Digest::SHA3_256.digest(MIX_ADDRESS_PREFIX + payload)[0...4]
133
133
  data = payload + checksum
134
134
  data = Base58.binary_to_base58 data, :bitcoin
135
135
  self.address = "#{MIX_ADDRESS_PREFIX}#{data}"
@@ -171,7 +171,7 @@ module MixinBot
171
171
 
172
172
  def encode
173
173
  msg = MAIN_ADDRESS_PREFIX + public_key
174
- checksum = SHA3::Digest::SHA256.digest msg
174
+ checksum = SHA3::Digest::SHA3_256.digest msg
175
175
  data = public_key + checksum[0...4]
176
176
  base58 = Base58.binary_to_base58 data, :bitcoin
177
177
  self.address = "#{MAIN_ADDRESS_PREFIX}#{base58}"
@@ -188,7 +188,7 @@ module MixinBot
188
188
  payload = data[...-4]
189
189
 
190
190
  msg = MAIN_ADDRESS_PREFIX + payload
191
- checksum = SHA3::Digest::SHA256.digest msg
191
+ checksum = SHA3::Digest::SHA3_256.digest msg
192
192
 
193
193
  raise ArgumentError, 'invalid address' unless checksum[0...4] == data[-4..]
194
194
 
@@ -200,8 +200,8 @@ module MixinBot
200
200
  def self.burning_address
201
201
  seed = "\0" * 64
202
202
 
203
- digest1 = SHA3::Digest::SHA256.digest seed
204
- digest2 = SHA3::Digest::SHA256.digest digest1
203
+ digest1 = SHA3::Digest::SHA3_256.digest seed
204
+ digest2 = SHA3::Digest::SHA3_256.digest digest1
205
205
  src = digest1 + digest2
206
206
 
207
207
  spend_key = MixinBot::Utils.shared_public_key(seed)
@@ -6,7 +6,7 @@ module MixinBot
6
6
  def safe_deposit_entries(**kwargs)
7
7
  path = '/safe/deposit/entries'
8
8
 
9
- members = [kwargs[:members]] if kwargs[:members].is_a? String
9
+ members = Array(kwargs[:members])
10
10
 
11
11
  payload = {
12
12
  members:,
@@ -10,7 +10,7 @@ module MixinBot
10
10
  Faye::WebSocket::Client.new(
11
11
  format('wss://%<host>s/', host: config.blaze_host),
12
12
  ['Mixin-Blaze-1'],
13
- headers: { 'Authorization' => authorization },
13
+ headers: { 'Authorization' => authorization, 'User-Agent' => "mixin_bot/#{MixinBot::VERSION}" },
14
14
  ping: 60
15
15
  )
16
16
  end
@@ -126,8 +126,8 @@ module MixinBot
126
126
  ciphertext = encrypter.update(Base64.urlsafe_decode64(data)) + encrypter.final + encrypter.auth_tag
127
127
 
128
128
  bytes = [1]
129
- bytes += [sessions.size].pack('v*').bytes
130
- bytes += JOSE::JWA::Ed25519.pk_to_curve25519(pk).bytes
129
+ bytes.concat([sessions.size].pack('v*').bytes)
130
+ bytes.concat(JOSE::JWA::Ed25519.pk_to_curve25519(pk).bytes)
131
131
 
132
132
  sessions.each do |session|
133
133
  aes_key = JOSE::JWA::X25519.shared_secret(
@@ -143,12 +143,12 @@ module MixinBot
143
143
  iv = encrypter.random_iv
144
144
  encrypter.iv = iv
145
145
 
146
- bytes += (MixinBot::UUID.new(hex: session['session_id']).packed + iv).bytes
147
- bytes += encrypter.update(key + padtext).bytes
146
+ bytes.concat((MixinBot::UUID.new(hex: session['session_id']).packed + iv).bytes)
147
+ bytes.concat(encrypter.update(key + padtext).bytes)
148
148
  end
149
149
 
150
- bytes += nounce.bytes
151
- bytes += ciphertext.bytes
150
+ bytes.concat(nounce.bytes)
151
+ bytes.concat(ciphertext.bytes)
152
152
 
153
153
  Base64.urlsafe_encode64 bytes.pack('C*'), padding: false
154
154
  end
@@ -89,7 +89,7 @@ module MixinBot
89
89
  end
90
90
  end
91
91
 
92
- asset = asset_mixin_id || SHA3::Digest::SHA256.hexdigest(asset_id)
92
+ asset = asset_mixin_id || SHA3::Digest::SHA3_256.hexdigest(asset_id)
93
93
  {
94
94
  version:,
95
95
  asset:,
@@ -10,12 +10,15 @@ module MixinBot
10
10
  kl = keystore.with_indifferent_access
11
11
  priv = OpenSSL::PKey::RSA.new(kl[:private_key])
12
12
  token = Base64.decode64(kl[:pin_token])
13
+ # Use string keys with the dash form (`'oaep-label'`) for Ruby 4.0
14
+ # compatibility; the underscore form `oaep_label` is no longer
15
+ # recognized by the OpenSSL EVP_PKEY_CTX in Ruby 4.0.
13
16
  key_bytes = priv.decrypt(
14
17
  token,
15
- rsa_padding_mode: 'oaep',
16
- rsa_oaep_md: 'sha256',
17
- rsa_mgf1_md: 'sha1',
18
- oaep_label: kl[:session_id]
18
+ 'rsa_padding_mode' => 'oaep',
19
+ 'rsa_oaep_md' => 'sha256',
20
+ 'rsa_mgf1_md' => 'sha1',
21
+ 'oaep-label' => kl[:session_id]
19
22
  )
20
23
 
21
24
  pin_byte = kl[:pin].to_s.b
@@ -12,6 +12,9 @@ module MixinBot
12
12
  trace_id = kwargs[:trace_id] || SecureRandom.uuid
13
13
 
14
14
  mix_address = MixinBot.utils.build_mix_address(members:, threshold:)
15
+ # Format amount without scientific notation so the Mixin web UI's URL
16
+ # parser accepts it. Mirrors `MixinBot::Utils::Address#build_safe_recipient`.
17
+ amount = format('%.8f', amount.to_d.to_r).gsub(/\.?0+\z/, '')
15
18
 
16
19
  "https://mixin.one/pay/#{mix_address}?amount=#{amount}&asset=#{asset_id}&memo=#{memo}&trace=#{trace_id}"
17
20
  end
@@ -138,7 +138,7 @@ module MixinBot
138
138
  aid = h[:asset_id]
139
139
  raise ArgumentError, 'utxo asset_id or asset is required' if aid.blank?
140
140
 
141
- SHA3::Digest::SHA256.hexdigest(aid)
141
+ SHA3::Digest::SHA3_256.hexdigest(aid)
142
142
  end
143
143
 
144
144
  asset = mixin_asset_for.call(utxos[0])
@@ -164,9 +164,9 @@ module MixinBot
164
164
  signing_key_hex = keypair[1].unpack1('H*')
165
165
 
166
166
  # NOTE: the Go SDK's +crypto.Sha256Hash+ is misleadingly named — it
167
- # actually computes SHA3-256, so +SHA3::Digest::SHA256+ is the correct
167
+ # actually computes SHA3-256, so +SHA3::Digest::SHA3_256+ is the correct
168
168
  # match. See bot-api-go-client safe_user.go +RegisterSafeBareUser+.
169
- app_id_hash = SHA3::Digest::SHA256.hexdigest config.app_id
169
+ app_id_hash = SHA3::Digest::SHA3_256.hexdigest config.app_id
170
170
  signature = Base64.urlsafe_encode64(
171
171
  JOSE::JWA::Ed25519.sign([app_id_hash].pack('H*'), keypair[1]),
172
172
  padding: false
@@ -54,7 +54,7 @@ module MixinBot
54
54
  payload = payload.pack('C*')
55
55
 
56
56
  # Calculate checksum
57
- checksum = SHA3::Digest::SHA256.digest(INVOICE_PREFIX + payload)[0...4]
57
+ checksum = SHA3::Digest::SHA3_256.digest(INVOICE_PREFIX + payload)[0...4]
58
58
 
59
59
  # Combine everything and encode to base64
60
60
  self.address = INVOICE_PREFIX + Base64.urlsafe_encode64(payload + checksum, padding: false)
@@ -68,7 +68,7 @@ module MixinBot
68
68
  raise MixinBot::InvalidInvoiceFormatError, 'invalid invoice payload size' if data.size < 3 + 23 + 1
69
69
 
70
70
  payload = data[...-4]
71
- checksum = SHA3::Digest::SHA256.digest(INVOICE_PREFIX + payload)[0...4]
71
+ checksum = SHA3::Digest::SHA3_256.digest(INVOICE_PREFIX + payload)[0...4]
72
72
  raise MixinBot::InvalidInvoiceFormatError, 'invalid invoice checksum' unless checksum == data[-4..]
73
73
 
74
74
  payload = payload.bytes
@@ -141,29 +141,29 @@ module MixinBot
141
141
  def encode
142
142
  bytes = []
143
143
 
144
- bytes += MixinBot::UUID.new(hex: trace_id).packed.bytes
145
- bytes += MixinBot::UUID.new(hex: asset_id).packed.bytes
144
+ bytes.concat MixinBot::UUID.new(hex: trace_id).packed.bytes
145
+ bytes.concat MixinBot::UUID.new(hex: asset_id).packed.bytes
146
146
 
147
147
  amount_string = amount.to_d.to_s('F')
148
148
  amount_bytes = amount_string.bytes
149
- bytes += MixinBot.utils.encode_int(amount_bytes.size)
150
- bytes += amount_bytes
149
+ bytes.concat MixinBot.utils.encode_int(amount_bytes.size)
150
+ bytes.concat amount_bytes
151
151
 
152
152
  extra_bytes = extra.bytes
153
- bytes += MixinBot.utils.encode_uint16(extra_bytes.size)
154
- bytes += extra_bytes
153
+ bytes.concat MixinBot.utils.encode_uint16(extra_bytes.size)
154
+ bytes.concat extra_bytes
155
155
 
156
156
  references_count = (index_references || []).size + (hash_references || []).size
157
- bytes += MixinBot.utils.encode_int(references_count)
157
+ bytes.concat MixinBot.utils.encode_int(references_count)
158
158
 
159
159
  index_references&.each do |index|
160
- bytes += MixinBot.utils.encode_int(1)
161
- bytes += MixinBot.utils.encode_int(index)
160
+ bytes.concat MixinBot.utils.encode_int(1)
161
+ bytes.concat MixinBot.utils.encode_int(index)
162
162
  end
163
163
 
164
164
  hash_references&.each do |hash|
165
- bytes += MixinBot.utils.encode_int(0)
166
- bytes += [hash].pack('H*').bytes
165
+ bytes.concat MixinBot.utils.encode_int(0)
166
+ bytes.concat [hash].pack('H*').bytes
167
167
  end
168
168
 
169
169
  bytes
data/lib/mixin_bot/nfo.rb CHANGED
@@ -39,10 +39,10 @@ module MixinBot
39
39
 
40
40
  def unique_token_id
41
41
  bytes = []
42
- bytes += MixinBot::UUID.new(hex: chain).packed.bytes
43
- bytes += [nm_class].pack('H*').bytes
44
- bytes += MixinBot::UUID.new(hex: collection).packed.bytes
45
- bytes += MixinBot.utils.encode_int token
42
+ bytes.concat MixinBot::UUID.new(hex: chain).packed.bytes
43
+ bytes.concat [nm_class].pack('H*').bytes
44
+ bytes.concat MixinBot::UUID.new(hex: collection).packed.bytes
45
+ bytes.concat MixinBot.utils.encode_int token
46
46
 
47
47
  md5 = Digest::MD5.new
48
48
  md5.update bytes.pack('c*')
@@ -67,33 +67,33 @@ module MixinBot
67
67
  def encode
68
68
  bytes = []
69
69
 
70
- bytes += prefix.bytes
71
- bytes += [version]
70
+ bytes.concat prefix.bytes
71
+ bytes.push(version)
72
72
 
73
73
  if mask.zero?
74
- bytes += [0]
74
+ bytes << 0
75
75
  else
76
- bytes += [1]
77
- bytes += MixinBot.utils.encode_uint64 mask
78
- bytes += MixinBot::UUID.new(hex: chain).packed.bytes
76
+ bytes << 1
77
+ bytes.concat MixinBot.utils.encode_uint64 mask
78
+ bytes.concat MixinBot::UUID.new(hex: chain).packed.bytes
79
79
 
80
80
  class_bytes = [nm_class].pack('H*').bytes
81
- bytes += MixinBot.utils.encode_int class_bytes.size
82
- bytes += class_bytes
81
+ bytes.concat MixinBot.utils.encode_int class_bytes.size
82
+ bytes.concat class_bytes
83
83
 
84
84
  collection_bytes = collection.split('-').pack('H* H* H* H* H*').bytes
85
- bytes += MixinBot.utils.encode_int collection_bytes.size
86
- bytes += collection_bytes
85
+ bytes.concat MixinBot.utils.encode_int collection_bytes.size
86
+ bytes.concat collection_bytes
87
87
 
88
88
  # token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
89
89
  token_bytes = MixinBot.utils.encode_int token
90
- bytes += MixinBot.utils.encode_int token_bytes.size
91
- bytes += token_bytes
90
+ bytes.concat MixinBot.utils.encode_int token_bytes.size
91
+ bytes.concat token_bytes
92
92
  end
93
93
 
94
94
  extra_bytes = [extra].pack('H*').bytes
95
- bytes += MixinBot.utils.encode_int extra_bytes.size
96
- bytes += extra_bytes
95
+ bytes.concat MixinBot.utils.encode_int extra_bytes.size
96
+ bytes.concat extra_bytes
97
97
 
98
98
  @raw = bytes.pack('C*')
99
99
  @hex = raw.unpack1('H*')
@@ -12,7 +12,7 @@ module MixinBot
12
12
 
13
13
  def decode
14
14
  raw = [@tx.hex].pack('H*').bytes
15
- @tx.hash = SHA3::Digest::SHA256.hexdigest(raw.pack('C*'))
15
+ @tx.hash = SHA3::Digest::SHA3_256.hexdigest(raw.pack('C*'))
16
16
  @buf = Buffer.new(raw)
17
17
 
18
18
  magic = @buf.shift(2)
@@ -17,28 +17,25 @@ module MixinBot
17
17
 
18
18
  bytes = []
19
19
 
20
- bytes += Transaction::MAGIC
21
- bytes += [0, @tx.version]
22
- bytes += [@tx.asset].pack('H*').bytes
20
+ bytes.concat Transaction::MAGIC
21
+ bytes.push(0, @tx.version)
22
+ bytes.concat [@tx.asset].pack('H*').bytes
23
23
 
24
- bytes += encode_inputs
25
- bytes += encode_outputs
26
- bytes += encode_references if @tx.version >= Transaction::REFERENCES_TX_VERSION
24
+ bytes.concat encode_inputs
25
+ bytes.concat encode_outputs
26
+ bytes.concat encode_references if @tx.version >= Transaction::REFERENCES_TX_VERSION
27
27
 
28
28
  extra_bytes = @tx.extra.bytes
29
29
  raise InvalidTransactionFormatError, 'extra is too long' if extra_bytes.size > Transaction::MAX_EXTRA_SIZE
30
30
 
31
- bytes += MixinBot.utils.encode_uint32 extra_bytes.size
32
- bytes += extra_bytes
31
+ bytes.concat MixinBot.utils.encode_uint32 extra_bytes.size
32
+ bytes.concat extra_bytes
33
33
 
34
- bytes += if @tx.aggregated.nil?
35
- encode_signatures
36
- else
37
- encode_aggregated_signature
38
- end
34
+ bytes.concat(@tx.aggregated.nil? ? encode_signatures : encode_aggregated_signature)
39
35
 
40
- @tx.hash = SHA3::Digest::SHA256.hexdigest bytes.pack('C*')
41
- @tx.hex = bytes.pack('C*').unpack1('H*')
36
+ packed = bytes.pack('C*')
37
+ @tx.hash = SHA3::Digest::SHA3_256.hexdigest packed
38
+ @tx.hex = packed.unpack1('H*')
42
39
 
43
40
  @tx
44
41
  end
@@ -48,63 +45,63 @@ module MixinBot
48
45
  def encode_inputs
49
46
  bytes = []
50
47
 
51
- bytes += MixinBot.utils.encode_uint16(@tx.inputs.size)
48
+ bytes.concat MixinBot.utils.encode_uint16(@tx.inputs.size)
52
49
 
53
50
  @tx.inputs.each do |input|
54
- bytes += [input['hash']].pack('H*').bytes
55
- bytes += MixinBot.utils.encode_uint16(input['index'])
51
+ bytes.concat [input['hash']].pack('H*').bytes
52
+ bytes.concat MixinBot.utils.encode_uint16(input['index'])
56
53
 
57
54
  genesis = input['genesis'] || ''
58
55
  if genesis.empty?
59
- bytes += Transaction::NULL_BYTES
56
+ bytes.concat Transaction::NULL_BYTES
60
57
  else
61
58
  genesis_bytes = [genesis].pack('H*').bytes
62
- bytes += MixinBot.utils.encode_uint16 genesis_bytes.size
63
- bytes += genesis_bytes
59
+ bytes.concat MixinBot.utils.encode_uint16 genesis_bytes.size
60
+ bytes.concat genesis_bytes
64
61
  end
65
62
 
66
63
  deposit = input['deposit']
67
64
  if deposit.nil?
68
- bytes += Transaction::NULL_BYTES
65
+ bytes.concat Transaction::NULL_BYTES
69
66
  else
70
- bytes += Transaction::MAGIC
71
- bytes += [deposit['chain']].pack('H*').bytes
67
+ bytes.concat Transaction::MAGIC
68
+ bytes.concat [deposit['chain']].pack('H*').bytes
72
69
 
73
70
  asset_bytes = [deposit['asset']].pack('H*')
74
- bytes += MixinBot.utils.encode_uint16 asset_bytes.size
75
- bytes += asset_bytes.bytes
71
+ bytes.concat MixinBot.utils.encode_uint16 asset_bytes.size
72
+ bytes.concat asset_bytes.bytes
76
73
 
77
74
  transaction_bytes = [deposit['transaction']].pack('H*')
78
- bytes += MixinBot.utils.encode_uint16 transaction_bytes.size
79
- bytes += transaction_bytes.bytes
75
+ bytes.concat MixinBot.utils.encode_uint16 transaction_bytes.size
76
+ bytes.concat transaction_bytes.bytes
80
77
 
81
- bytes += MixinBot.utils.encode_uint64 deposit['index']
78
+ bytes.concat MixinBot.utils.encode_uint64 deposit['index']
82
79
 
83
80
  amount_bytes = MixinBot.utils.bytes_of deposit['amount']
84
- bytes += MixinBot.utils.encode_uint16 amount_bytes.size
85
- bytes += amount_bytes
81
+ bytes.concat MixinBot.utils.encode_uint16 amount_bytes.size
82
+ bytes.concat amount_bytes
86
83
  end
87
84
 
88
85
  mint = input['mint']
89
86
  if mint.nil?
90
- bytes += Transaction::NULL_BYTES
87
+ bytes.concat Transaction::NULL_BYTES
91
88
  else
92
- bytes += Transaction::MAGIC
89
+ bytes.concat Transaction::MAGIC
93
90
 
94
91
  group = mint['group'] || ''
95
92
  if group.empty?
96
- bytes += Transaction::NULL_BYTES
93
+ bytes.concat Transaction::NULL_BYTES
97
94
  else
98
95
  group_bytes = [group].pack('H*')
99
- bytes += MixinBot.utils.encode_uint16 group_bytes.size
100
- bytes += group_bytes.bytes
96
+ bytes.concat MixinBot.utils.encode_uint16 group_bytes.size
97
+ bytes.concat group_bytes.bytes
101
98
  end
102
99
 
103
- bytes += MixinBot.utils.encode_uint64 mint['batch']
100
+ bytes.concat MixinBot.utils.encode_uint64 mint['batch']
104
101
 
105
102
  amount_bytes = MixinBot.utils.encode_int mint['amount']
106
- bytes += MixinBot.utils.encode_uint16 amount_bytes.size
107
- bytes += amount_bytes
103
+ bytes.concat MixinBot.utils.encode_uint16 amount_bytes.size
104
+ bytes.concat amount_bytes
108
105
  end
109
106
  end
110
107
 
@@ -114,55 +111,55 @@ module MixinBot
114
111
  def encode_outputs
115
112
  bytes = []
116
113
 
117
- bytes += MixinBot.utils.encode_uint16 @tx.outputs.size
114
+ bytes.concat MixinBot.utils.encode_uint16 @tx.outputs.size
118
115
 
119
116
  @tx.outputs.each do |output|
120
117
  type = output['type'] || 0
121
- bytes += [0x00, type]
118
+ bytes.push(0x00, type)
122
119
 
123
120
  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
121
+ bytes.concat MixinBot.utils.encode_uint16 amount_bytes.size
122
+ bytes.concat amount_bytes
126
123
 
127
- bytes += MixinBot.utils.encode_uint16 output['keys'].size
124
+ bytes.concat MixinBot.utils.encode_uint16 output['keys'].size
128
125
  output['keys'].each do |key|
129
- bytes += [key].pack('H*').bytes
126
+ bytes.concat [key].pack('H*').bytes
130
127
  end
131
128
 
132
- bytes += [output['mask']].pack('H*').bytes
129
+ bytes.concat [output['mask']].pack('H*').bytes
133
130
 
134
131
  script_bytes = [output['script']].pack('H*').bytes
135
- bytes += MixinBot.utils.encode_uint16 script_bytes.size
136
- bytes += script_bytes
132
+ bytes.concat MixinBot.utils.encode_uint16 script_bytes.size
133
+ bytes.concat script_bytes
137
134
 
138
135
  withdrawal = output['withdrawal']
139
136
  if withdrawal.nil?
140
- bytes += Transaction::NULL_BYTES
137
+ bytes.concat Transaction::NULL_BYTES
141
138
  else
142
- bytes += Transaction::MAGIC
139
+ bytes.concat Transaction::MAGIC
143
140
 
144
- bytes += [withdrawal['chain']].pack('H*').bytes
141
+ bytes.concat [withdrawal['chain']].pack('H*').bytes
145
142
 
146
143
  asset_bytes = [withdrawal['asset']].pack('H*')
147
- bytes += MixinBot.utils.encode_uint16 asset_bytes.bytesize
148
- bytes += asset_bytes.bytes
144
+ bytes.concat MixinBot.utils.encode_uint16 asset_bytes.bytesize
145
+ bytes.concat asset_bytes.bytes
149
146
 
150
147
  address = withdrawal['address'] || ''
151
148
  if address.empty?
152
- bytes += Transaction::NULL_BYTES
149
+ bytes.concat Transaction::NULL_BYTES
153
150
  else
154
151
  address_bytes = [address].pack('H*').bytes
155
- bytes += MixinBot.utils.encode_uint16 address_bytes.size
156
- bytes += address_bytes
152
+ bytes.concat MixinBot.utils.encode_uint16 address_bytes.size
153
+ bytes.concat address_bytes
157
154
  end
158
155
 
159
156
  tag = withdrawal['tag'] || ''
160
157
  if tag.empty?
161
- bytes += Transaction::NULL_BYTES
158
+ bytes.concat Transaction::NULL_BYTES
162
159
  else
163
160
  tag_bytes = [tag].pack('H*').bytes
164
- bytes += MixinBot.utils.encode_uint16 tag_bytes.size
165
- bytes += tag_bytes
161
+ bytes.concat MixinBot.utils.encode_uint16 tag_bytes.size
162
+ bytes.concat tag_bytes
166
163
  end
167
164
  end
168
165
  end
@@ -174,10 +171,10 @@ module MixinBot
174
171
  bytes = []
175
172
 
176
173
  references = Array(@tx.references)
177
- bytes += MixinBot.utils.encode_uint16 references.size
174
+ bytes.concat MixinBot.utils.encode_uint16 references.size
178
175
 
179
176
  references.each do |reference|
180
- bytes += [reference].pack('H*').bytes
177
+ bytes.concat [reference].pack('H*').bytes
181
178
  end
182
179
 
183
180
  bytes
@@ -186,14 +183,14 @@ module MixinBot
186
183
  def encode_aggregated_signature
187
184
  bytes = []
188
185
 
189
- bytes += MixinBot.utils.encode_uint16 Transaction::MAX_ENCODE_INT
190
- bytes += MixinBot.utils.encode_uint16 Transaction::AGGREGATED_SIGNATURE_PREFIX
191
- bytes += [@tx.aggregated['signature']].pack('H*').bytes
186
+ bytes.concat MixinBot.utils.encode_uint16 Transaction::MAX_ENCODE_INT
187
+ bytes.concat MixinBot.utils.encode_uint16 Transaction::AGGREGATED_SIGNATURE_PREFIX
188
+ bytes.concat [@tx.aggregated['signature']].pack('H*').bytes
192
189
 
193
190
  signers = @tx.aggregated['signers'] || []
194
191
  if signers.empty?
195
- bytes += Transaction::AGGREGATED_SIGNATURE_ORDINAY_MASK
196
- bytes += Transaction::NULL_BYTES
192
+ bytes.concat Transaction::AGGREGATED_SIGNATURE_ORDINAY_MASK
193
+ bytes.concat Transaction::NULL_BYTES
197
194
  return bytes
198
195
  end
199
196
 
@@ -205,17 +202,17 @@ module MixinBot
205
202
  max = signers.last
206
203
  sig_byte_len = [@tx.aggregated['signature']].pack('H*').bytes.size
207
204
  if ((max / 8) + 1) > sig_byte_len
208
- bytes += Transaction::AGGREGATED_SIGNATURE_SPARSE_MASK
209
- bytes += MixinBot.utils.encode_uint16 signers.size
210
- signers.each { |signer| bytes += MixinBot.utils.encode_uint16(signer) }
205
+ bytes.concat Transaction::AGGREGATED_SIGNATURE_SPARSE_MASK
206
+ bytes.concat MixinBot.utils.encode_uint16 signers.size
207
+ signers.each { |signer| bytes.concat MixinBot.utils.encode_uint16(signer) }
211
208
  else
212
209
  masks_bytes = Array.new((max / 8) + 1, 0)
213
210
  signers.each do |signer|
214
211
  masks_bytes[signer / 8] ^= (1 << (signer % 8))
215
212
  end
216
- bytes += Transaction::AGGREGATED_SIGNATURE_ORDINAY_MASK
217
- bytes += MixinBot.utils.encode_uint16 masks_bytes.size
218
- bytes += masks_bytes
213
+ bytes.concat Transaction::AGGREGATED_SIGNATURE_ORDINAY_MASK
214
+ bytes.concat MixinBot.utils.encode_uint16 masks_bytes.size
215
+ bytes.concat masks_bytes
219
216
  end
220
217
 
221
218
  bytes
@@ -233,18 +230,18 @@ module MixinBot
233
230
 
234
231
  raise ArgumentError, 'signatures overflow' if sl == Transaction::MAX_ENCODE_INT
235
232
 
236
- bytes += MixinBot.utils.encode_uint16 sl
233
+ bytes.concat MixinBot.utils.encode_uint16 sl
237
234
 
238
235
  if sl.positive?
239
236
  @tx.signatures.each do |signature|
240
- bytes += MixinBot.utils.encode_uint16 signature.keys.size
237
+ bytes.concat MixinBot.utils.encode_uint16 signature.keys.size
241
238
 
242
239
  signature.keys.sort.each do |key|
243
240
  signature_bytes = [signature[key]].pack('H*').bytes
244
241
  raise ArgumentError, 'Signature should be 64 bytes' if signature_bytes.size != 64
245
242
 
246
- bytes += MixinBot.utils.encode_uint16 key
247
- bytes += signature_bytes
243
+ bytes.concat MixinBot.utils.encode_uint16 key
244
+ bytes.concat signature_bytes
248
245
  end
249
246
  end
250
247
  end
@@ -52,7 +52,7 @@ module MixinBot
52
52
  #
53
53
  def hash_members(ids)
54
54
  list = Array(ids).flatten.compact.map(&:to_s).sort
55
- SHA3::Digest::SHA256.hexdigest(list.join)
55
+ SHA3::Digest::SHA3_256.hexdigest(list.join)
56
56
  end
57
57
  end
58
58
  end
@@ -11,5 +11,5 @@ module MixinBot
11
11
  #
12
12
  # @see https://semver.org/
13
13
  #
14
- VERSION = '2.3.0'
14
+ VERSION = '2.4.0'
15
15
  end
data/lib/mvm/registry.rb CHANGED
@@ -53,9 +53,9 @@ module MVM
53
53
 
54
54
  def contract_from_multisig(user_ids, threshold)
55
55
  bytes = []
56
- bytes += MixinBot.utils.encode_uint16(user_ids.length)
57
- bytes += [user_ids.sort.join.gsub('-', '')].pack('H*').bytes
58
- bytes += MixinBot.utils.encode_uint16(threshold)
56
+ bytes.concat(MixinBot.utils.encode_uint16(user_ids.length))
57
+ bytes.concat([user_ids.sort.join.gsub('-', '')].pack('H*').bytes)
58
+ bytes.concat(MixinBot.utils.encode_uint16(threshold))
59
59
 
60
60
  hash = Eth::Util.bin_to_prefixed_hex(Eth::Util.keccak256(bytes.pack('C*')))
61
61
  @rpc.call @registry, 'contracts', hash.to_i(16)
data/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # MixinBot
2
2
 
3
- > Ruby SDK and CLI for Mixin Network: Safe UTXO transfers, REST API, Blaze messaging, transaction crypto, optional MVM helpers. Ruby >= 3.2. Gem version 2.2.1.
3
+ > Ruby SDK and CLI for Mixin Network: Safe UTXO transfers, REST API, Blaze messaging, transaction crypto, optional MVM helpers. Ruby >= 3.2. Gem version 2.3.0.
4
4
 
5
5
  Important notes:
6
6
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixin_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - an-lee
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-05-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -232,14 +231,14 @@ dependencies:
232
231
  requirements:
233
232
  - - "~>"
234
233
  - !ruby/object:Gem::Version
235
- version: '1.0'
234
+ version: '2.2'
236
235
  type: :runtime
237
236
  prerelease: false
238
237
  version_requirements: !ruby/object:Gem::Requirement
239
238
  requirements:
240
239
  - - "~>"
241
240
  - !ruby/object:Gem::Version
242
- version: '1.0'
241
+ version: '2.2'
243
242
  - !ruby/object:Gem::Dependency
244
243
  name: thor
245
244
  requirement: !ruby/object:Gem::Requirement
@@ -375,7 +374,6 @@ licenses:
375
374
  - MIT
376
375
  metadata:
377
376
  rubygems_mfa_required: 'true'
378
- post_install_message:
379
377
  rdoc_options: []
380
378
  require_paths:
381
379
  - lib
@@ -390,8 +388,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
390
388
  - !ruby/object:Gem::Version
391
389
  version: '0'
392
390
  requirements: []
393
- rubygems_version: 3.5.22
394
- signing_key:
391
+ rubygems_version: 4.0.16
395
392
  specification_version: 4
396
393
  summary: A Ruby SDK for Mixin Network
397
394
  test_files: []