mixin_bot 2.1.0 → 2.2.1

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: ee9a02bff6da3b4331ff084e75d4f94d10d0a98b50e7b2c899679ec78340c659
4
+ data.tar.gz: 699c595d8f4acfd94041be582b0eda32db2ea4ba4da34359d1fc231d7e6bcbc2
5
5
  SHA512:
6
- metadata.gz: 1cb159a3d41d2b09bd2880023df2beeb2af8c3fd82d702f6abb8e1cdcfe77833a7ac52e067e04f913ff304d3c6f0ab302810534ab98da67f396ccae4af1845cb
7
- data.tar.gz: f917dbae1725dd89b83ddc8e8139e104660509e3bc978870b483441c824912b244ec474895b8b1a05edc95c726c1631ee4b1d4b01455bff410bc5f07c6d4b6cb
6
+ metadata.gz: ad0a66de09acfda9b1a4da94568ec4d704eab3be8f4eb92de145262bd0e74fc4b0efaee1243b16d5876199d270160c2fbd48aced6580af3308ab55117e156205
7
+ data.tar.gz: de638caf39c1af94cb169887f424d48127971f8bcd74c60a3f1b9e338b974c867001931e649244f16523e7d8efa501c6d81f0604c25f04a891cc7412550a45ed
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.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).
4
4
 
5
5
  ## Commands
6
6
 
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.2.1] - 2026-05-24
11
+
12
+ ### Changed
13
+
14
+ - **Billing preflight `increment`** — `ensure_app_billing_credit!` accepts an `increment` parameter (default `0`) instead of reading `app_properties.price`. `create_user` / `create_safe_user` default to `0.5` per billed user; pass `increment: 0` for free-tier headroom.
15
+
16
+ ## [2.2.0] - 2026-05-24
17
+
18
+ ### Added
19
+
20
+ - **`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`.
21
+ - **`MixinBot::InsufficientAppBillingError`** — structured fields: `app_id`, `credit`, `cost`, `increment`.
22
+ - **CLI** — `mixinbot call create_user ... --force` skips billing preflight; billing failures map to structured error kind `billing`.
23
+
10
24
  ## [2.1.0] - 2026-05-24
11
25
 
12
26
  ### 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.1** (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,38 @@ 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
+ # @param increment [Numeric, String] estimated cost added to total billing
33
+ # cost for headroom (default +0+)
34
+ # @raise [InsufficientAppBillingError] when +credit+ is not greater than
35
+ # total cost plus +increment+
36
+ #
37
+ def ensure_app_billing_credit!(force: false, access_token: nil, increment: 0)
38
+ return if force
39
+
40
+ app_id = config.app_id
41
+ billing = app_billing(app_id, access_token:)['data']
42
+
43
+ credit = billing_decimal billing['credit']
44
+ cost_users = billing_decimal billing.dig('cost', 'users')
45
+ cost_resources = billing_decimal billing.dig('cost', 'resources')
46
+ cost = cost_users + cost_resources
47
+ increment = billing_decimal increment
48
+
49
+ return if credit > cost + increment
50
+
51
+ raise InsufficientAppBillingError.new(
52
+ app_id:,
53
+ credit: credit.to_s('F'),
54
+ cost: cost.to_s('F'),
55
+ increment: increment.to_s('F')
56
+ )
57
+ end
58
+
27
59
  def create_app(**kwargs)
28
60
  payload = {
29
61
  redirect_uri: kwargs[:redirect_uri],
@@ -95,6 +127,12 @@ module MixinBot
95
127
  client.post path, user_id: receiver_user_id, pin_base64: tip[:pin_base64] || tip[:pin], access_token:
96
128
  end
97
129
  alias migrate transfer_app_ownership
130
+
131
+ private
132
+
133
+ def billing_decimal(value)
134
+ BigDecimal(value.to_s)
135
+ end
98
136
  end
99
137
  end
100
138
  end
@@ -23,6 +23,10 @@ module MixinBot
23
23
  # the new TIP PIN has time to propagate on the server side.
24
24
  TIP_PIN_PROPAGATION_DELAY = 1
25
25
 
26
+ # Billed cost per network user created via {#create_user} (USD), after
27
+ # the free tier. Pass +increment: 0+ to skip headroom for the new user.
28
+ CREATE_USER_BILLING_INCREMENT = '0.5'
29
+
26
30
  def user(user_id, access_token: nil)
27
31
  path = format('/users/%<user_id>s', user_id:)
28
32
  client.get path, access_token:
@@ -37,9 +41,16 @@ module MixinBot
37
41
  #
38
42
  # @param full_name [String] display name for the new user
39
43
  # @param key [String, nil] optional 32-byte Ed25519 seed
44
+ # @param force [Boolean] when false (default), verify app billing credit
45
+ # headroom before calling the API; when true, skip the preflight
46
+ # @param increment [Numeric, String] billing headroom for the new user
47
+ # (defaults to {CREATE_USER_BILLING_INCREMENT}; use +0+ on free tier)
40
48
  # @return [Hash] Mixin response merged with the hex-encoded private key
49
+ # @raise [InsufficientAppBillingError] when billing credit lacks headroom
41
50
  #
42
- def create_user(full_name, key: nil)
51
+ def create_user(full_name, key: nil, force: false, increment: CREATE_USER_BILLING_INCREMENT)
52
+ ensure_app_billing_credit!(force:, increment:)
53
+
43
54
  keypair = JOSE::JWA::Ed25519.keypair key
44
55
  session_secret = Base64.urlsafe_encode64 keypair[0], padding: false
45
56
  private_key = keypair[1].unpack1('H*')
@@ -85,19 +96,24 @@ module MixinBot
85
96
  # @param name [String] display name for the new user
86
97
  # @param private_key [String, nil] optional 32-byte session Ed25519 seed
87
98
  # @param spend_key [String, nil] optional 32-byte spend Ed25519 seed
99
+ # @param force [Boolean] forwarded to {#create_user}; see billing preflight
100
+ # there
101
+ # @param increment [Numeric, String] forwarded to {#create_user}
88
102
  # @return [Hash] keystore with +:app_id+, +:session_id+,
89
103
  # +:session_private_key+, +:server_public_key+ and +:spend_key+
90
104
  # @raise [MixinBot::Error] when registration ultimately fails. Transient
91
105
  # PIN/response errors are retried up to {SAFE_REGISTER_MAX_RETRIES}
92
106
  # times; other errors bubble up immediately.
107
+ # @raise [InsufficientAppBillingError] when {#create_user} billing
108
+ # preflight fails
93
109
  #
94
- def create_safe_user(name, private_key: nil, spend_key: nil)
110
+ def create_safe_user(name, private_key: nil, spend_key: nil, force: false, increment: CREATE_USER_BILLING_INCREMENT)
95
111
  session_keypair = JOSE::JWA::Ed25519.keypair private_key
96
112
  spend_keypair = JOSE::JWA::Ed25519.keypair spend_key
97
113
 
98
114
  spend_key_hex = spend_keypair[1].unpack1('H*')
99
115
 
100
- user = create_user name, key: session_keypair[1][...32]
116
+ user = create_user name, key: session_keypair[1][...32], force: force, increment: increment
101
117
  data = user.fetch('data')
102
118
 
103
119
  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.1'
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.1.
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - an-lee