mixin_bot 2.1.0 → 2.2.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: 7b32c36a1629b320052d174d39a6330439f726c44dd78ff98b7fdb20d517fe1a
4
- data.tar.gz: 0bc0cade506ab4db2aada6776bcf860f4a01ae3db8b83e530fa79b5d91755a12
3
+ metadata.gz: 2bbc251caedc50e5324a90713c291cf019b2bf0fa003a7ad4df3401af5b175fa
4
+ data.tar.gz: cb7e858d5073bb2fc8bd5e487027fc10a684b8ff05dbc2949aa48270808e1f0a
5
5
  SHA512:
6
- metadata.gz: 1cb159a3d41d2b09bd2880023df2beeb2af8c3fd82d702f6abb8e1cdcfe77833a7ac52e067e04f913ff304d3c6f0ab302810534ab98da67f396ccae4af1845cb
7
- data.tar.gz: f917dbae1725dd89b83ddc8e8139e104660509e3bc978870b483441c824912b244ec474895b8b1a05edc95c726c1631ee4b1d4b01455bff410bc5f07c6d4b6cb
6
+ metadata.gz: 758781312e8be1e922eee338ed4ddfd3154c44ea25fb1d7e2b67d9e3ea8298381e4cc4d6df33904c35b831545d136da14c4af1630fdabe7789d3761a1617d6d9
7
+ data.tar.gz: a5350e9812115ef75e39a906e6fb48aa55189692887ee037c0dc3f29cf67b00e70b912575fbe5b9d1f44d93be29c520c2a3142804671c1bcfd3bc96dcfee6b9a
data/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — MixinBot
2
2
 
3
- Ruby gem (v2.1.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).
3
+ Ruby gem (v2.2.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,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.2.0] - 2026-05-24
11
+
12
+ ### Added
13
+
14
+ - **`MixinBot::API#create_user` billing preflight** — verifies app billing headroom (`credit > cost + next user fee`) via `app_billing` and `app_properties` before `POST /users`. Raises `InsufficientAppBillingError` by default; pass `force: true` to skip. `create_safe_user` forwards `force:` to `create_user`.
15
+ - **`MixinBot::InsufficientAppBillingError`** — structured fields: `app_id`, `credit`, `cost`, `increment`.
16
+ - **CLI** — `mixinbot call create_user ... --force` skips billing preflight; billing failures map to structured error kind `billing`.
17
+
10
18
  ## [2.1.0] - 2026-05-24
11
19
 
12
20
  ### 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.1.0** (see [CHANGELOG.md](CHANGELOG.md) for breaking changes and deprecations).
9
+ Current gem version: **2.2.0** (see [CHANGELOG.md](CHANGELOG.md) for breaking changes and deprecations).
10
10
 
11
11
  ## Requirements
12
12
 
data/docs/agent/cli.md CHANGED
@@ -72,7 +72,7 @@ Error (stderr, exit 1):
72
72
  }
73
73
  ```
74
74
 
75
- Error kinds: `invalid_args`, `auth`, `not_found`, `api_error`, `unsupported`, `conflict`, `internal`.
75
+ Error kinds: `invalid_args`, `auth`, `not_found`, `api_error`, `billing`, `unsupported`, `conflict`, `internal`.
76
76
 
77
77
  ## Commands
78
78
 
@@ -106,8 +106,12 @@ List JSON shape:
106
106
  mixinbot call me -k keystore.json -o json
107
107
  mixinbot call safe_outputs -k keystore.json -d '{"asset":"965e5c6e-434c-3fa9-b780-c50f43cd955c","state":"unspent","limit":10}' -o json
108
108
  mixinbot call user USER_UUID -k keystore.json --data-only -o json
109
+ mixinbot call create_user "Bot User" -k keystore.json -o json
110
+ mixinbot call create_user "Bot User" -k keystore.json --force -o json
109
111
  ```
110
112
 
113
+ `create_user` performs a client-side app billing preflight by default. When credit lacks headroom for the next billed user, the CLI returns `"kind": "billing"`. Use `--force` to skip the preflight (or pass `"force": true` in `-d`; `-d` wins when both are set).
114
+
111
115
  ### Raw HTTP
112
116
 
113
117
  ```bash
@@ -24,6 +24,37 @@ module MixinBot
24
24
  client.get path, access_token:
25
25
  end
26
26
 
27
+ ##
28
+ # Verifies the app has billing headroom before a billed operation (e.g.
29
+ # creating a network user). Skipped when +force+ is true.
30
+ #
31
+ # @param force [Boolean] skip the preflight and call the API anyway
32
+ # @raise [InsufficientAppBillingError] when +credit+ is not greater than
33
+ # total cost plus the next user fee from {app_properties}
34
+ #
35
+ def ensure_app_billing_credit!(force: false, access_token: nil)
36
+ return if force
37
+
38
+ app_id = config.app_id
39
+ billing = app_billing(app_id, access_token:)['data']
40
+ properties = app_properties(access_token:)['data']
41
+
42
+ credit = billing_decimal billing['credit']
43
+ cost_users = billing_decimal billing.dig('cost', 'users')
44
+ cost_resources = billing_decimal billing.dig('cost', 'resources')
45
+ cost = cost_users + cost_resources
46
+ increment = billing_decimal properties['price']
47
+
48
+ return if credit > cost + increment
49
+
50
+ raise InsufficientAppBillingError.new(
51
+ app_id:,
52
+ credit: credit.to_s('F'),
53
+ cost: cost.to_s('F'),
54
+ increment: increment.to_s('F')
55
+ )
56
+ end
57
+
27
58
  def create_app(**kwargs)
28
59
  payload = {
29
60
  redirect_uri: kwargs[:redirect_uri],
@@ -95,6 +126,12 @@ module MixinBot
95
126
  client.post path, user_id: receiver_user_id, pin_base64: tip[:pin_base64] || tip[:pin], access_token:
96
127
  end
97
128
  alias migrate transfer_app_ownership
129
+
130
+ private
131
+
132
+ def billing_decimal(value)
133
+ BigDecimal(value.to_s)
134
+ end
98
135
  end
99
136
  end
100
137
  end
@@ -37,9 +37,14 @@ module MixinBot
37
37
  #
38
38
  # @param full_name [String] display name for the new user
39
39
  # @param key [String, nil] optional 32-byte Ed25519 seed
40
+ # @param force [Boolean] when false (default), verify app billing credit
41
+ # headroom before calling the API; when true, skip the preflight
40
42
  # @return [Hash] Mixin response merged with the hex-encoded private key
43
+ # @raise [InsufficientAppBillingError] when billing credit lacks headroom
41
44
  #
42
- def create_user(full_name, key: nil)
45
+ def create_user(full_name, key: nil, force: false)
46
+ ensure_app_billing_credit!(force:)
47
+
43
48
  keypair = JOSE::JWA::Ed25519.keypair key
44
49
  session_secret = Base64.urlsafe_encode64 keypair[0], padding: false
45
50
  private_key = keypair[1].unpack1('H*')
@@ -85,19 +90,23 @@ module MixinBot
85
90
  # @param name [String] display name for the new user
86
91
  # @param private_key [String, nil] optional 32-byte session Ed25519 seed
87
92
  # @param spend_key [String, nil] optional 32-byte spend Ed25519 seed
93
+ # @param force [Boolean] forwarded to {#create_user}; see billing preflight
94
+ # there
88
95
  # @return [Hash] keystore with +:app_id+, +:session_id+,
89
96
  # +:session_private_key+, +:server_public_key+ and +:spend_key+
90
97
  # @raise [MixinBot::Error] when registration ultimately fails. Transient
91
98
  # PIN/response errors are retried up to {SAFE_REGISTER_MAX_RETRIES}
92
99
  # times; other errors bubble up immediately.
100
+ # @raise [InsufficientAppBillingError] when {#create_user} billing
101
+ # preflight fails
93
102
  #
94
- def create_safe_user(name, private_key: nil, spend_key: nil)
103
+ def create_safe_user(name, private_key: nil, spend_key: nil, force: false)
95
104
  session_keypair = JOSE::JWA::Ed25519.keypair private_key
96
105
  spend_keypair = JOSE::JWA::Ed25519.keypair spend_key
97
106
 
98
107
  spend_key_hex = spend_keypair[1].unpack1('H*')
99
108
 
100
- user = create_user name, key: session_keypair[1][...32]
109
+ user = create_user name, key: session_keypair[1][...32], force: force
101
110
  data = user.fetch('data')
102
111
 
103
112
  keystore = {
@@ -15,11 +15,13 @@ module MixinBot
15
15
  LONGDESC
16
16
  option :keystore, type: :string, aliases: '-k', desc: 'keystore JSON file path or inline JSON'
17
17
  option :data, type: :string, aliases: '-d', default: '{}', desc: 'JSON object of keyword arguments'
18
+ option :force, type: :boolean, default: false, desc: 'Skip billing preflight for create_user (see -d force to override)'
18
19
  option :data_only, type: :boolean, default: false, desc: 'Print only the data field of API responses'
19
20
  def call(method_name, *positional)
20
21
  with_command_name('call') do
21
22
  setup_api_instance!
22
23
  kwargs = parse_json_data(options[:data])
24
+ kwargs = merge_call_force_kwargs(method_name, kwargs)
23
25
  result = invoke_api(method_name, kwargs:, positional:)
24
26
  print_result(result, data_only: options[:data_only], command: 'call')
25
27
  end
@@ -55,6 +57,14 @@ module MixinBot
55
57
 
56
58
  private
57
59
 
60
+ def merge_call_force_kwargs(method_name, kwargs)
61
+ return kwargs unless method_name.to_sym == :create_user
62
+ return kwargs if kwargs.key?(:force)
63
+ return kwargs unless options[:force]
64
+
65
+ kwargs.merge(force: true)
66
+ end
67
+
58
68
  def print_pretty_list(items, total, limit, offset)
59
69
  grouped = items.group_by { |item| item['owner'] }
60
70
  grouped.sort_by { |owner, _| owner }.each do |owner, names|
@@ -12,6 +12,7 @@ module MixinBot
12
12
  api_error: { retryable: false, description: 'Mixin API returned an error' },
13
13
  unsupported: { retryable: false, description: 'Operation is not supported in this context' },
14
14
  conflict: { retryable: false, description: 'Resource exists with incompatible configuration' },
15
+ billing: { retryable: false, description: 'App billing credit insufficient for the operation' },
15
16
  internal: { retryable: false, description: 'Unexpected internal error' }
16
17
  }.freeze
17
18
 
@@ -35,6 +36,8 @@ module MixinBot
35
36
  :auth
36
37
  when NotFoundError, UserNotFoundError
37
38
  :not_found
39
+ when InsufficientAppBillingError
40
+ :billing
38
41
  when ResponseError, RequestError, HttpError,
39
42
  InsufficientBalanceError, UtxoInsufficientError, InsufficientPoolError
40
43
  :api_error
@@ -51,6 +51,26 @@ module MixinBot
51
51
  #
52
52
  class InsufficientBalanceError < Error; end
53
53
 
54
+ ##
55
+ # Raised when app prepaid billing credit lacks headroom for a billed operation.
56
+ #
57
+ class InsufficientAppBillingError < Error
58
+ attr_reader :app_id, :credit, :cost, :increment
59
+
60
+ def initialize(app_id:, credit:, cost:, increment:)
61
+ @app_id = app_id
62
+ @credit = credit
63
+ @cost = cost
64
+ @increment = increment
65
+ super(
66
+ format(
67
+ 'app billing insufficient: credit %<credit>s <= cost %<cost>s + increment %<increment>s (app_id=%<app_id>s)',
68
+ credit:, cost:, increment:, app_id:
69
+ )
70
+ )
71
+ end
72
+ end
73
+
54
74
  ##
55
75
  # Raised when selected UTXOs cannot cover the requested amount (mirrors Go +UtxoInsufficientError+).
56
76
  #
@@ -11,5 +11,5 @@ module MixinBot
11
11
  #
12
12
  # @see https://semver.org/
13
13
  #
14
- VERSION = '2.1.0'
14
+ VERSION = '2.2.0'
15
15
  end
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.1.0.
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.0.
4
4
 
5
5
  Important notes:
6
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixin_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - an-lee