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
data/lib/mixin_bot/api.rb CHANGED
@@ -8,10 +8,16 @@ require_relative 'api/asset'
8
8
  require_relative 'api/attachment'
9
9
  require_relative 'api/auth'
10
10
  require_relative 'api/blaze'
11
+ require_relative 'api/chain'
12
+ require_relative 'api/code'
13
+ require_relative 'api/computer_api'
11
14
  require_relative 'api/conversation'
15
+ require_relative 'api/deposit'
12
16
  require_relative 'api/encrypted_message'
17
+ require_relative 'api/fiat'
13
18
  require_relative 'api/inscription'
14
19
  require_relative 'api/legacy_collectible'
20
+ require_relative 'api/legacy_user'
15
21
  require_relative 'api/legacy_multisig'
16
22
  require_relative 'api/legacy_output'
17
23
  require_relative 'api/legacy_payment'
@@ -21,21 +27,155 @@ require_relative 'api/legacy_transfer'
21
27
  require_relative 'api/me'
22
28
  require_relative 'api/message'
23
29
  require_relative 'api/multisig'
30
+ require_relative 'api/network'
31
+ require_relative 'api/network_asset'
24
32
  require_relative 'api/output'
25
33
  require_relative 'api/payment'
34
+ require_relative 'api/pin_payload'
26
35
  require_relative 'api/pin'
27
36
  require_relative 'api/rpc'
37
+ require_relative 'api/session'
28
38
  require_relative 'api/snapshot'
29
39
  require_relative 'api/tip'
30
40
  require_relative 'api/transaction'
31
41
  require_relative 'api/transfer'
42
+ require_relative 'api/turn'
32
43
  require_relative 'api/user'
33
44
  require_relative 'api/withdraw'
34
45
 
35
46
  module MixinBot
47
+ ##
48
+ # Main API interface for interacting with Mixin Network.
49
+ #
50
+ # The API class provides access to all Mixin Network endpoints including:
51
+ # - User and bot profile management
52
+ # - Asset management (read assets, check balances)
53
+ # - Transfers and payments (Safe API and legacy)
54
+ # - Messaging (send/receive messages via Blaze)
55
+ # - Conversations and encrypted messages
56
+ # - Multisig operations
57
+ # - NFT and collectible operations
58
+ # - Transaction building and signing
59
+ # - Withdrawal operations
60
+ #
61
+ # == Usage
62
+ #
63
+ # === Using Global Configuration
64
+ #
65
+ # MixinBot.configure do
66
+ # self.app_id = 'your-app-id'
67
+ # self.session_id = 'your-session-id'
68
+ # self.session_private_key = 'your-private-key'
69
+ # self.server_public_key = 'server-public-key'
70
+ # end
71
+ #
72
+ # # Access via global instance
73
+ # MixinBot.api.me
74
+ # MixinBot.api.assets
75
+ #
76
+ # === Creating Dedicated Instances
77
+ #
78
+ # api = MixinBot::API.new(
79
+ # app_id: 'your-app-id',
80
+ # session_id: 'your-session-id',
81
+ # session_private_key: 'your-private-key',
82
+ # server_public_key: 'server-public-key'
83
+ # )
84
+ #
85
+ # api.me
86
+ # api.assets
87
+ #
88
+ # == API Categories
89
+ #
90
+ # === Profile & Users
91
+ # - me, safe_me, update_me - Bot profile operations
92
+ # - read_user, read_users, search_user - User lookup
93
+ # - friends - List bot friends
94
+ #
95
+ # === Assets & Balance
96
+ # - assets, asset - Read asset information
97
+ # - ticker - Get asset ticker data
98
+ # - safe_assets - Read Safe API assets
99
+ #
100
+ # === Transfers & Payments
101
+ # - create_transfer, create_safe_transfer - Send payments
102
+ # - build_safe_transaction - Build raw transactions
103
+ # - sign_safe_transaction - Sign transactions
104
+ # - send_safe_transaction - Submit signed transactions
105
+ #
106
+ # === Messaging
107
+ # - start_blaze_connect - Connect to Blaze WebSocket
108
+ # - send_message - Send text/data messages
109
+ # - send_encrypted_messages - Send encrypted messages
110
+ #
111
+ # === Multisig & UTXOs
112
+ # - safe_outputs - Read unspent outputs
113
+ # - multisig_payments - Multisig payment operations
114
+ # - safe_ghost_keys - Generate ghost keys
115
+ #
116
+ # === NFT & Collectibles
117
+ # - create_collectible_request - Create NFT requests
118
+ # - read_collectibles - Read collectible tokens
119
+ # - inscriptions - Inscription operations
120
+ #
121
+ # == Examples
122
+ #
123
+ # Get bot information:
124
+ #
125
+ # profile = MixinBot.api.me
126
+ # puts profile['full_name']
127
+ #
128
+ # Read assets:
129
+ #
130
+ # assets = MixinBot.api.assets
131
+ # assets.each do |asset|
132
+ # puts "#{asset['symbol']}: #{asset['balance']}"
133
+ # end
134
+ #
135
+ # Send a transfer:
136
+ #
137
+ # result = MixinBot.api.create_transfer(
138
+ # members: ['recipient-user-id'],
139
+ # threshold: 1,
140
+ # asset_id: 'asset-uuid',
141
+ # amount: '0.01',
142
+ # memo: 'Payment for services',
143
+ # trace_id: SecureRandom.uuid
144
+ # )
145
+ #
36
146
  class API
37
- attr_reader :config, :client
147
+ ##
148
+ # @return [MixinBot::Configuration] the configuration for this API instance
149
+ attr_reader :config
38
150
 
151
+ ##
152
+ # @return [MixinBot::Client] the HTTP client for making API requests
153
+ attr_reader :client
154
+
155
+ ##
156
+ # Initializes a new API instance.
157
+ #
158
+ # If no parameters are provided, uses the global MixinBot configuration.
159
+ # Otherwise, creates a new configuration with the provided parameters.
160
+ #
161
+ # @param kwargs [Hash] configuration options (see Configuration#initialize)
162
+ # @option kwargs [String] :app_id the application ID
163
+ # @option kwargs [String] :session_id the session ID
164
+ # @option kwargs [String] :session_private_key the session private key
165
+ # @option kwargs [String] :server_public_key the server public key
166
+ # @option kwargs [String] :spend_key the spend private key (for Safe API)
167
+ #
168
+ # @example Using global configuration
169
+ # api = MixinBot::API.new
170
+ #
171
+ # @example With custom configuration
172
+ # api = MixinBot::API.new(
173
+ # app_id: 'your-app-id',
174
+ # session_id: 'your-session-id',
175
+ # session_private_key: 'your-private-key',
176
+ # server_public_key: 'server-public-key'
177
+ # )
178
+ #
39
179
  def initialize(**kwargs)
40
180
  @config =
41
181
  if kwargs.present?
@@ -47,14 +187,42 @@ module MixinBot
47
187
  @client = Client.new(@config)
48
188
  end
49
189
 
190
+ ##
191
+ # Provides access to utility methods.
192
+ #
193
+ # @return [Module] the Utils module
194
+ #
50
195
  def utils
51
196
  MixinBot::Utils
52
197
  end
53
198
 
199
+ ##
200
+ # Returns the client ID (same as app_id).
201
+ #
202
+ # @return [String] the client ID
203
+ #
54
204
  def client_id
55
205
  config.app_id
56
206
  end
57
207
 
208
+ ##
209
+ # Generates an access token for API authentication.
210
+ #
211
+ # Creates a JWT token signed with the bot's private key for authenticating
212
+ # API requests. The token includes request details and has a limited lifetime.
213
+ #
214
+ # @param method [String] the HTTP method (GET, POST, etc.)
215
+ # @param uri [String] the request URI path
216
+ # @param body [String] the request body
217
+ # @param kwargs [Hash] additional options
218
+ # @option kwargs [Integer] :exp_in (600) token expiration time in seconds
219
+ # @option kwargs [String] :scp ('FULL') token scope
220
+ #
221
+ # @return [String] the JWT access token
222
+ #
223
+ # @example
224
+ # token = api.access_token('GET', '/me', '')
225
+ #
58
226
  def access_token(method, uri, body, **kwargs)
59
227
  utils.access_token(
60
228
  method,
@@ -67,20 +235,71 @@ module MixinBot
67
235
  private_key: config.session_private_key
68
236
  )
69
237
  end
238
+ alias sign_authentication_token access_token
239
+ alias sign_authentication_token_without_body access_token
240
+ alias sign_authentication_token_with_request_id access_token
70
241
 
242
+ ##
243
+ # Encodes a transaction hash to raw transaction format.
244
+ #
245
+ # @param txn [Hash] the transaction hash with keys: version, asset, inputs, outputs, extra
246
+ # @return [String] the hex-encoded raw transaction
247
+ #
248
+ # @example
249
+ # raw = api.encode_raw_transaction(
250
+ # version: 5,
251
+ # asset: 'asset-id',
252
+ # inputs: [...],
253
+ # outputs: [...],
254
+ # extra: 'memo'
255
+ # )
256
+ #
71
257
  def encode_raw_transaction(txn)
72
258
  utils.encode_raw_transaction txn
73
259
  end
74
260
 
261
+ ##
262
+ # Decodes a raw transaction to a hash.
263
+ #
264
+ # @param raw [String] the hex-encoded raw transaction
265
+ # @return [Hash] the decoded transaction
266
+ #
267
+ # @example
268
+ # txn = api.decode_raw_transaction(raw_hex)
269
+ # puts txn['asset']
270
+ # puts txn['inputs']
271
+ #
75
272
  def decode_raw_transaction(raw)
76
273
  utils.decode_raw_transaction raw
77
274
  end
78
275
 
276
+ ##
277
+ # Generates a trace ID from a transaction hash.
278
+ #
279
+ # Creates a deterministic UUID trace ID from a transaction hash,
280
+ # useful for tracking outputs from a transaction.
281
+ #
282
+ # @param hash [String] the transaction hash
283
+ # @param output_index [Integer] the output index (default: 0)
284
+ # @return [String] the generated trace UUID
285
+ #
286
+ # @example
287
+ # trace_id = api.generate_trace_from_hash(tx_hash, 0)
288
+ #
79
289
  def generate_trace_from_hash(hash, output_index = 0)
80
290
  utils.generate_trace_from_hash hash, output_index
81
291
  end
82
292
 
83
- # Use a mixin software to implement transaction build
293
+ ##
294
+ # Encodes a raw transaction using native mixin command-line tool.
295
+ #
296
+ # Requires the 'mixin' command to be installed and available in PATH.
297
+ # This is an alternative to the Ruby implementation.
298
+ #
299
+ # @param json [String] the transaction JSON
300
+ # @return [String] the encoded raw transaction
301
+ # @raise [RuntimeError] if mixin command is not available
302
+ #
84
303
  def encode_raw_transaction_native(json)
85
304
  ensure_mixin_command_exist
86
305
  command = format("mixin signrawtransaction --raw '%<arg>s'", arg: json)
@@ -91,7 +310,16 @@ module MixinBot
91
310
  output.chomp
92
311
  end
93
312
 
94
- # Use a mixin software to implement transaction build
313
+ ##
314
+ # Decodes a raw transaction using native mixin command-line tool.
315
+ #
316
+ # Requires the 'mixin' command to be installed and available in PATH.
317
+ # This is an alternative to the Ruby implementation.
318
+ #
319
+ # @param raw [String] the hex-encoded raw transaction
320
+ # @return [Hash] the decoded transaction
321
+ # @raise [RuntimeError] if mixin command is not available
322
+ #
95
323
  def decode_raw_transaction_native(raw)
96
324
  ensure_mixin_command_exist
97
325
  command = format("mixin decoderawtransaction --raw '%<arg>s'", arg: raw)
@@ -108,10 +336,16 @@ module MixinBot
108
336
  include MixinBot::API::Attachment
109
337
  include MixinBot::API::Auth
110
338
  include MixinBot::API::Blaze
339
+ include MixinBot::API::Chain
340
+ include MixinBot::API::Code
341
+ include MixinBot::API::ComputerApi
111
342
  include MixinBot::API::Conversation
343
+ include MixinBot::API::Deposit
112
344
  include MixinBot::API::EncryptedMessage
345
+ include MixinBot::API::Fiat
113
346
  include MixinBot::API::Inscription
114
347
  include MixinBot::API::LegacyCollectible
348
+ include MixinBot::API::LegacyUser
115
349
  include MixinBot::API::LegacyMultisig
116
350
  include MixinBot::API::LegacyOutput
117
351
  include MixinBot::API::LegacyPayment
@@ -121,19 +355,30 @@ module MixinBot
121
355
  include MixinBot::API::Me
122
356
  include MixinBot::API::Message
123
357
  include MixinBot::API::Multisig
358
+ include MixinBot::API::Network
359
+ include MixinBot::API::NetworkAsset
124
360
  include MixinBot::API::Output
125
361
  include MixinBot::API::Payment
126
362
  include MixinBot::API::Pin
127
363
  include MixinBot::API::Rpc
364
+ include MixinBot::API::Session
128
365
  include MixinBot::API::Snapshot
129
366
  include MixinBot::API::Tip
367
+ include MixinBot::API::PinPayload
130
368
  include MixinBot::API::Transaction
131
369
  include MixinBot::API::Transfer
370
+ include MixinBot::API::Turn
132
371
  include MixinBot::API::User
133
372
  include MixinBot::API::Withdraw
134
373
 
135
374
  private
136
375
 
376
+ def warn_legacy_mixin_api!(api_label)
377
+ MixinBot.deprecator.warn(
378
+ "MixinBot legacy API #{api_label} is deprecated; migrate to the Safe API. See CHANGELOG for 2.0.0."
379
+ )
380
+ end
381
+
137
382
  def ensure_mixin_command_exist
138
383
  return if command?('mixin')
139
384
 
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ # Bot platform request signing (parity with Go BotAuthClient).
5
+ class BotAuth
6
+ class MapCache
7
+ def initialize
8
+ @store = {}
9
+ end
10
+
11
+ def get(key)
12
+ @store[key]
13
+ end
14
+
15
+ def put(key, value)
16
+ @store[key] = value
17
+ end
18
+
19
+ def delete(key)
20
+ @store.delete(key)
21
+ end
22
+ end
23
+
24
+ class Client
25
+ PLATFORM_PREFIX = 'up_'
26
+
27
+ def initialize(api, cache: MapCache.new)
28
+ @api = api
29
+ @cache = cache
30
+ end
31
+
32
+ def sign_request(timestamp, bot_user_id, method, uri, body = nil)
33
+ shared_key = shared_key_for(bot_user_id)
34
+ data = "#{timestamp}#{method}#{uri}"
35
+ data += body.to_s if body.present?
36
+ digest = OpenSSL::HMAC.digest('SHA256', shared_key, data)
37
+ Base64.urlsafe_encode64(@api.config.app_id.b + digest, padding: false)
38
+ end
39
+
40
+ private
41
+
42
+ def shared_key_for(user_id)
43
+ cached = @cache.get(user_id)
44
+ return cached if cached.present? && cached.bytesize >= 32
45
+
46
+ sessions = @api.fetch_user_sessions([user_id])['data']
47
+ session = Array(sessions).first
48
+ raise MixinBot::NotFoundError, "no session for #{user_id}" if session.nil?
49
+
50
+ u_pk = Base64.urlsafe_decode64(session['public_key'])
51
+ sk = @api.config.session_private_key_curve25519
52
+ shared = JOSE::JWA::X25519.x25519(sk, u_pk[0, 32])
53
+ @cache.put(user_id, shared)
54
+ @cache.put("#{PLATFORM_PREFIX}#{user_id}", session['platform'].to_s.b) if session['platform']
55
+ shared
56
+ end
57
+ end
58
+
59
+ def self.new_map_cache
60
+ MapCache.new
61
+ end
62
+
63
+ def self.new_client(api, cache: MapCache.new)
64
+ Client.new(api, cache:)
65
+ end
66
+
67
+ def self.new_default_client(api, cache: MapCache.new)
68
+ new_client(api, cache:)
69
+ end
70
+ end
71
+ end