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.
- checksums.yaml +4 -4
- data/AGENTS.md +75 -0
- data/API_COVERAGE.md +143 -0
- data/CHANGELOG.md +112 -0
- data/README.md +375 -0
- data/docs/agent/cli.md +149 -0
- data/docs/agent/cookbook.md +152 -0
- data/examples/blaze.rb +43 -0
- data/examples/config.yml.example +21 -0
- data/lib/mixin_bot/address.rb +43 -3
- data/lib/mixin_bot/api/app.rb +7 -0
- data/lib/mixin_bot/api/asset.rb +114 -3
- data/lib/mixin_bot/api/auth.rb +19 -10
- data/lib/mixin_bot/api/blaze.rb +81 -0
- data/lib/mixin_bot/api/chain.rb +94 -0
- data/lib/mixin_bot/api/code.rb +16 -0
- data/lib/mixin_bot/api/computer_api.rb +60 -0
- data/lib/mixin_bot/api/conversation.rb +7 -1
- data/lib/mixin_bot/api/deposit.rb +12 -0
- data/lib/mixin_bot/api/encrypted_message.rb +1 -1
- data/lib/mixin_bot/api/fiat.rb +12 -0
- data/lib/mixin_bot/api/inscription.rb +2 -2
- data/lib/mixin_bot/api/legacy_collectible.rb +26 -27
- data/lib/mixin_bot/api/legacy_multisig.rb +20 -21
- data/lib/mixin_bot/api/legacy_output.rb +10 -3
- data/lib/mixin_bot/api/legacy_payment.rb +2 -0
- data/lib/mixin_bot/api/legacy_snapshot.rb +16 -0
- data/lib/mixin_bot/api/legacy_transaction.rb +28 -13
- data/lib/mixin_bot/api/legacy_transfer.rb +11 -8
- data/lib/mixin_bot/api/legacy_user.rb +51 -0
- data/lib/mixin_bot/api/me.rb +99 -3
- data/lib/mixin_bot/api/message.rb +18 -27
- data/lib/mixin_bot/api/multisig.rb +19 -0
- data/lib/mixin_bot/api/network.rb +17 -0
- data/lib/mixin_bot/api/network_asset.rb +27 -0
- data/lib/mixin_bot/api/output.rb +1 -1
- data/lib/mixin_bot/api/pin.rb +16 -3
- data/lib/mixin_bot/api/pin_payload.rb +26 -0
- data/lib/mixin_bot/api/session.rb +14 -0
- data/lib/mixin_bot/api/snapshot.rb +6 -0
- data/lib/mixin_bot/api/tip.rb +74 -1
- data/lib/mixin_bot/api/transaction.rb +106 -17
- data/lib/mixin_bot/api/transfer.rb +141 -14
- data/lib/mixin_bot/api/turn.rb +12 -0
- data/lib/mixin_bot/api/user.rb +148 -45
- data/lib/mixin_bot/api/withdraw.rb +24 -23
- data/lib/mixin_bot/api.rb +248 -3
- data/lib/mixin_bot/bot_auth.rb +71 -0
- data/lib/mixin_bot/cli/api.rb +224 -143
- data/lib/mixin_bot/cli/base.rb +77 -0
- data/lib/mixin_bot/cli/call.rb +71 -0
- data/lib/mixin_bot/cli/errors.rb +56 -0
- data/lib/mixin_bot/cli/node.rb +9 -2
- data/lib/mixin_bot/cli/output.rb +196 -0
- data/lib/mixin_bot/cli/schema.rb +274 -0
- data/lib/mixin_bot/cli/schema_command.rb +21 -0
- data/lib/mixin_bot/cli/utils.rb +114 -18
- data/lib/mixin_bot/cli.rb +124 -48
- data/lib/mixin_bot/client/error_mapper.rb +40 -0
- data/lib/mixin_bot/client.rb +94 -64
- data/lib/mixin_bot/computer.rb +132 -0
- data/lib/mixin_bot/configuration.rb +108 -1
- data/lib/mixin_bot/errors.rb +102 -0
- data/lib/mixin_bot/models/address.rb +11 -0
- data/lib/mixin_bot/models/api_envelope.rb +67 -0
- data/lib/mixin_bot/models/asset.rb +11 -0
- data/lib/mixin_bot/models/ghost_keys.rb +14 -0
- data/lib/mixin_bot/models/output.rb +11 -0
- data/lib/mixin_bot/models/safe_multisig_request.rb +11 -0
- data/lib/mixin_bot/models/sequencer_transaction_request.rb +11 -0
- data/lib/mixin_bot/models/user.rb +11 -0
- data/lib/mixin_bot/models.rb +10 -0
- data/lib/mixin_bot/monitor.rb +77 -0
- data/lib/mixin_bot/transaction/buffer.rb +34 -0
- data/lib/mixin_bot/transaction/decoder.rb +227 -0
- data/lib/mixin_bot/transaction/encoder.rb +255 -0
- data/lib/mixin_bot/transaction.rb +6 -475
- data/lib/mixin_bot/url_scheme.rb +63 -0
- data/lib/mixin_bot/utils/address.rb +17 -80
- data/lib/mixin_bot/utils/crypto.rb +173 -1
- data/lib/mixin_bot/utils/decoder.rb +1 -1
- data/lib/mixin_bot/utils/encoder.rb +13 -0
- data/lib/mixin_bot/utils.rb +45 -0
- data/lib/mixin_bot/uuid.rb +78 -1
- data/lib/mixin_bot/version.rb +11 -1
- data/lib/mixin_bot.rb +172 -18
- data/lib/mvm/bridge.rb +46 -0
- data/lib/mvm/client.rb +60 -0
- data/lib/mvm/nft.rb +4 -2
- data/lib/mvm/registry.rb +2 -1
- data/lib/mvm.rb +93 -0
- data/lib/tasks/api_coverage.rake +20 -0
- data/llms.txt +29 -0
- metadata +77 -9
data/examples/blaze.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require './lib/mixin_bot'
|
|
4
|
+
require 'base64'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
|
|
7
|
+
CONFIG = YAML.load_file("#{File.dirname __FILE__}/config.yml")
|
|
8
|
+
MixinBot.configure do
|
|
9
|
+
self.app_id = CONFIG['app_id']
|
|
10
|
+
self.client_secret = CONFIG['client_secret']
|
|
11
|
+
self.session_id = CONFIG['session_id']
|
|
12
|
+
self.server_public_key = CONFIG['server_public_key']
|
|
13
|
+
self.session_private_key = CONFIG['session_private_key']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# default connect
|
|
17
|
+
# EM.run {
|
|
18
|
+
# MixinBot.api.start_blaze_connect
|
|
19
|
+
# }
|
|
20
|
+
|
|
21
|
+
EM.run do
|
|
22
|
+
MixinBot.api.start_blaze_connect do
|
|
23
|
+
def on_open(blaze, _event)
|
|
24
|
+
p [Time.now.to_s, :on_open]
|
|
25
|
+
blaze.send list_pending_message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_message(blaze, event)
|
|
29
|
+
raw = JSON.parse ws_message(event.data)
|
|
30
|
+
p [Time.now.to_s, :on_message, raw&.[]('action')]
|
|
31
|
+
|
|
32
|
+
blaze.send acknowledge_message_receipt(raw['data']['message_id']) unless raw&.[]('data')&.[]('message_id').nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# def on_error(blaze, event)
|
|
36
|
+
# p [Time.now.to_s, :on_error]
|
|
37
|
+
# end
|
|
38
|
+
|
|
39
|
+
# def on_close(blaze, event)
|
|
40
|
+
# p [Time.now.to_s, :on_close, event.code, event.reason]
|
|
41
|
+
# end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
app_id: 0508a116-1239-4e28-b150-85a8e3e6b400
|
|
2
|
+
client_secret: d9dc58107bacde6713c12734663de6bbb2b83e917d5fe62167bda13ca6665b15
|
|
3
|
+
session_id: 25696f85-b7b4-4509-8c3f-2684a8fc4a2a
|
|
4
|
+
pin_code: "431005"
|
|
5
|
+
server_public_key: b0pjBUKI0Vp9K+NspaLCKKeV1IzuO5o2Zgzp2uriXYn6bKcKowhZ7nsjNT1mxtq82dsPzGficOM9H9n1HvNyilrKrpW5ndu7/dMsw/r3JvbexDvLZfAGmuAHcZOy+AwbMSJ/XurLJg1lS9w9PVkDltqWWy2TOunUlbe1lWYy/1M=
|
|
6
|
+
session_private_key: |
|
|
7
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
8
|
+
MIICXAIBAAKBgQDQYjiR/Te6Bh/1bk8gWRbQkrX0AIGPja1DLUQHu5Uw9M4P53O3
|
|
9
|
+
f4pDCGoN3R5+LYjODtquOwmEjcMhbhp6XarrnJVXH8WGmJcpjVwGtwIjPTeRMu4Z
|
|
10
|
+
8XWo314fO2pLeiqWbJ4knlGb0oiXxBghwF4KglUSElQ/FXjdKaabHCw0zQIDAQAB
|
|
11
|
+
AoGALgqFlTvtZByWUxPcR7lnYQ4JRbAW8DDNZ1pI/axkejyciscIujJjygvB4u5I
|
|
12
|
+
HnjRETYW+wfwQmlQA8Lf9slbSe1FlsajNWl/zc+bwoYTO3yfB81GWwk/e20dTGfs
|
|
13
|
+
2d8ITe+LlYUMlqZgBGr2Y2pHeXDvuzC6ZsKZQKoS3B2RscECQQD0RTXp0tNSbWE7
|
|
14
|
+
TzhXwZu3jkIRhZsajeDv+7VYMZ94TO1PGeQSi25jBT8JcDfbyXO8eZTtjARfTEmE
|
|
15
|
+
Ln6ejveHAkEA2mPb+TyZl0FqIluwo1KbCv3PVlaHDL+HyHoBsq+6yhn3VRMDbyqs
|
|
16
|
+
ZKViJxaLrzUx6+IQOr9T8LqFcaQhSrxeCwJANqMgewuoLwC+RejjXmW08erFBmxP
|
|
17
|
+
FDJ2BNfVaUO4Os1iK1ZMOIWtjEKJJhBOvj+iPp8nW7b852AF9aX8tnSeEwJAacH8
|
|
18
|
+
D7l6A5aJCDRw2NazAGKjGoNyiQjjf4Ed+2NASIjEjq1Td20p6N9yJc20PVe8Yieq
|
|
19
|
+
hliLFMOuxbae7KtFuwJBAI9xMuLBLWixwvezyFCAJpj93ClOgSIlM7qgEfUxLvjZ
|
|
20
|
+
YxyVoIU3haXJPYPgaVE9nEwwiNPRS5DJ2fCGT+oGfrY=
|
|
21
|
+
-----END RSA PRIVATE KEY-----
|
data/lib/mixin_bot/address.rb
CHANGED
|
@@ -10,6 +10,14 @@ module MixinBot
|
|
|
10
10
|
class MixAddress
|
|
11
11
|
attr_accessor :version, :uuid_members, :xin_members, :threshold, :address, :payload
|
|
12
12
|
|
|
13
|
+
def self.parse(string)
|
|
14
|
+
new(address: string)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.from_members(members:, threshold:)
|
|
18
|
+
new(members:, threshold:)
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
def initialize(**args)
|
|
14
22
|
args = args.with_indifferent_access
|
|
15
23
|
|
|
@@ -23,8 +31,9 @@ module MixinBot
|
|
|
23
31
|
@version = args[:version] || MIX_ADDRESS_VERSION
|
|
24
32
|
|
|
25
33
|
if args[:members].present?
|
|
26
|
-
@uuid_members = args[:members].
|
|
27
|
-
|
|
34
|
+
@xin_members, @uuid_members = args[:members].partition do |member|
|
|
35
|
+
member.start_with?(MAIN_ADDRESS_PREFIX)
|
|
36
|
+
end
|
|
28
37
|
else
|
|
29
38
|
@uuid_members = args[:uuid_members] || []
|
|
30
39
|
@xin_members = args[:xin_members] || []
|
|
@@ -53,11 +62,42 @@ module MixinBot
|
|
|
53
62
|
}
|
|
54
63
|
end
|
|
55
64
|
|
|
65
|
+
def request_or_generate_ghost_keys(output_index = 0, api: MixinBot.api)
|
|
66
|
+
if xin_members.present?
|
|
67
|
+
key = JOSE::JWA::Ed25519.keypair
|
|
68
|
+
gk = { 'mask' => key[0].unpack1('H*'), 'keys' => [] }
|
|
69
|
+
xin_members.each do |member|
|
|
70
|
+
payload = MixinBot.utils.parse_main_address(member)
|
|
71
|
+
spend_key = payload[0...32]
|
|
72
|
+
view_key = payload[-32..]
|
|
73
|
+
ghost = MixinBot.utils.derive_ghost_public_key(key[1], view_key, spend_key, output_index)
|
|
74
|
+
gk['keys'] << ghost.unpack1('H*')
|
|
75
|
+
end
|
|
76
|
+
gk
|
|
77
|
+
else
|
|
78
|
+
hint = SecureRandom.uuid
|
|
79
|
+
api.create_safe_keys(
|
|
80
|
+
{ receivers: (uuid_members + xin_members).sort, index: output_index, hint: }
|
|
81
|
+
)['data'].first
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
56
85
|
def encode
|
|
57
86
|
raise ArgumentError, 'members should be an array' unless uuid_members.is_a?(Array) || xin_members.is_a?(Array)
|
|
58
87
|
raise ArgumentError, 'members should not be empty' if uuid_members.empty? && xin_members.empty?
|
|
59
88
|
raise ArgumentError, 'members length should less than 256' if uuid_members.length + xin_members.length > 255
|
|
60
|
-
|
|
89
|
+
|
|
90
|
+
member_count = uuid_members.length + xin_members.length
|
|
91
|
+
# UUID mix (Go NewUUIDMixAddress): threshold must be in 1..len(members).
|
|
92
|
+
# XIN-only mix (Go NewMainnetMixAddress): threshold may exceed member count (sparse), e.g. storage 1-of-64 style.
|
|
93
|
+
if uuid_members.present? && xin_members.empty?
|
|
94
|
+
raise ArgumentError, "invalid threshold: #{threshold}" unless threshold.positive? && threshold <= member_count
|
|
95
|
+
elsif xin_members.present? && uuid_members.empty?
|
|
96
|
+
raise ArgumentError, "invalid threshold: #{threshold}" unless threshold.positive?
|
|
97
|
+
raise ArgumentError, 'too many XIN members' if member_count > 64
|
|
98
|
+
elsif threshold > member_count
|
|
99
|
+
raise ArgumentError, "invalid threshold: #{threshold}"
|
|
100
|
+
end
|
|
61
101
|
|
|
62
102
|
prefix =
|
|
63
103
|
[version].pack('C*') +
|
data/lib/mixin_bot/api/app.rb
CHANGED
|
@@ -20,6 +20,13 @@ module MixinBot
|
|
|
20
20
|
|
|
21
21
|
client.get path, access_token:
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
def transfer_app_ownership(receiver_user_id:, pin:, access_token: nil)
|
|
25
|
+
path = format('/apps/%<app_id>s/transfer', app_id: config.app_id)
|
|
26
|
+
tip = tip_or_legacy_pin_payload(pin, 'TIP:APP:OWNERSHIP:TRANSFER:', receiver_user_id)
|
|
27
|
+
client.post path, user_id: receiver_user_id, pin_base64: tip[:pin_base64] || tip[:pin], access_token:
|
|
28
|
+
end
|
|
29
|
+
alias migrate transfer_app_ownership
|
|
23
30
|
end
|
|
24
31
|
end
|
|
25
32
|
end
|
data/lib/mixin_bot/api/asset.rb
CHANGED
|
@@ -2,20 +2,101 @@
|
|
|
2
2
|
|
|
3
3
|
module MixinBot
|
|
4
4
|
class API
|
|
5
|
+
##
|
|
6
|
+
# API methods for asset management.
|
|
7
|
+
#
|
|
8
|
+
# Provides methods to:
|
|
9
|
+
# - List all assets in bot's wallet
|
|
10
|
+
# - Read specific asset information
|
|
11
|
+
# - Get asset ticker/price data
|
|
12
|
+
#
|
|
5
13
|
module Asset
|
|
6
|
-
|
|
14
|
+
##
|
|
15
|
+
# Retrieves all assets in the bot's wallet.
|
|
16
|
+
#
|
|
17
|
+
# Returns an array of asset objects, each containing:
|
|
18
|
+
# - asset_id: the asset UUID
|
|
19
|
+
# - symbol: the asset symbol (e.g., "BTC", "ETH")
|
|
20
|
+
# - name: the full asset name
|
|
21
|
+
# - icon_url: URL to the asset icon
|
|
22
|
+
# - balance: current balance
|
|
23
|
+
# - destination: deposit address
|
|
24
|
+
# - tag: deposit memo/tag (if applicable)
|
|
25
|
+
# - price_btc: price in BTC
|
|
26
|
+
# - price_usd: price in USD
|
|
27
|
+
# - chain_id: the blockchain UUID
|
|
28
|
+
# - change_btc: 24h price change in BTC
|
|
29
|
+
# - change_usd: 24h price change in USD
|
|
30
|
+
# - confirmations: required confirmations
|
|
31
|
+
# - asset_key: the asset key for deposit
|
|
32
|
+
#
|
|
33
|
+
# @param access_token [String, nil] optional access token
|
|
34
|
+
# @return [Array<Hash>] array of asset objects
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# assets = api.assets
|
|
38
|
+
# assets.each do |asset|
|
|
39
|
+
# puts "#{asset['symbol']}: #{asset['balance']}"
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# @see https://developers.mixin.one/docs/api/assets/assets
|
|
43
|
+
#
|
|
7
44
|
def assets(access_token: nil)
|
|
8
45
|
path = '/assets'
|
|
9
46
|
client.get path, access_token:
|
|
10
47
|
end
|
|
11
48
|
|
|
12
|
-
|
|
49
|
+
##
|
|
50
|
+
# Retrieves information for a specific asset.
|
|
51
|
+
#
|
|
52
|
+
# Returns detailed information about a single asset including:
|
|
53
|
+
# - Current balance
|
|
54
|
+
# - Price information
|
|
55
|
+
# - Deposit address
|
|
56
|
+
# - Network details
|
|
57
|
+
#
|
|
58
|
+
# @param asset_id [String] the asset UUID
|
|
59
|
+
# @param access_token [String, nil] optional access token
|
|
60
|
+
# @return [Hash] the asset information
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# # Get Bitcoin information
|
|
64
|
+
# btc = api.asset('c6d0c728-2624-429b-8e0d-d9d19b6592fa')
|
|
65
|
+
# puts "BTC Balance: #{btc['balance']}"
|
|
66
|
+
# puts "BTC Price: $#{btc['price_usd']}"
|
|
67
|
+
#
|
|
68
|
+
# @see https://developers.mixin.one/docs/api/assets/asset
|
|
69
|
+
#
|
|
13
70
|
def asset(asset_id, access_token: nil)
|
|
14
71
|
path = format('/assets/%<asset_id>s', asset_id:)
|
|
15
72
|
client.get path, access_token:
|
|
16
73
|
end
|
|
17
74
|
|
|
18
|
-
|
|
75
|
+
##
|
|
76
|
+
# Retrieves ticker/price data for an asset.
|
|
77
|
+
#
|
|
78
|
+
# Returns historical price and volume data for an asset,
|
|
79
|
+
# useful for charts and price tracking.
|
|
80
|
+
#
|
|
81
|
+
# @param asset_id [String] the asset UUID
|
|
82
|
+
# @param kwargs [Hash] query options
|
|
83
|
+
# @option kwargs [String, DateTime, Time] :offset the time offset for historical data
|
|
84
|
+
# @option kwargs [String] :access_token optional access token
|
|
85
|
+
# @return [Hash] ticker data including price and volume
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# # Get current ticker
|
|
89
|
+
# ticker = api.ticker('c6d0c728-2624-429b-8e0d-d9d19b6592fa')
|
|
90
|
+
# puts "Price: $#{ticker['price_usd']}"
|
|
91
|
+
#
|
|
92
|
+
# # Get historical ticker
|
|
93
|
+
# ticker = api.ticker(
|
|
94
|
+
# 'c6d0c728-2624-429b-8e0d-d9d19b6592fa',
|
|
95
|
+
# offset: 1.day.ago
|
|
96
|
+
# )
|
|
97
|
+
#
|
|
98
|
+
# @see https://developers.mixin.one/docs/api/assets/ticker
|
|
99
|
+
#
|
|
19
100
|
def ticker(asset_id, **kwargs)
|
|
20
101
|
offset = kwargs[:offset]
|
|
21
102
|
offset = DateTime.rfc3339(offset) if offset.is_a? String
|
|
@@ -24,6 +105,36 @@ module MixinBot
|
|
|
24
105
|
path = '/ticker'
|
|
25
106
|
client.get path, asset_id:, offset:, access_token: kwargs[:access_token]
|
|
26
107
|
end
|
|
108
|
+
|
|
109
|
+
def fetch_assets(asset_ids, access_token: nil)
|
|
110
|
+
client.fetch_post_array '/safe/assets/fetch', Array(asset_ids), access_token:
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def asset_fee(asset_id, destination:, access_token: nil)
|
|
114
|
+
path = format('/safe/assets/%<asset_id>s/fees', asset_id:)
|
|
115
|
+
client.get path, destination:, access_token:
|
|
116
|
+
end
|
|
117
|
+
alias read_asset_fee asset_fee
|
|
118
|
+
|
|
119
|
+
def asset_balance(asset_id)
|
|
120
|
+
outputs = safe_outputs(asset: asset_id, state: 'unspent')
|
|
121
|
+
Array(outputs['data']).sum { |o| o['amount'].to_d }
|
|
122
|
+
end
|
|
123
|
+
alias asset_balance_with_safe_user asset_balance
|
|
124
|
+
|
|
125
|
+
def user_asset_balance(user_id, asset_id, access_token: nil)
|
|
126
|
+
members_hash = MixinBot.utils.hash_members([user_id])
|
|
127
|
+
path = '/safe/outputs'
|
|
128
|
+
response = client.get(
|
|
129
|
+
path,
|
|
130
|
+
members: members_hash,
|
|
131
|
+
threshold: 1,
|
|
132
|
+
asset: asset_id,
|
|
133
|
+
state: 'unspent',
|
|
134
|
+
access_token:
|
|
135
|
+
)
|
|
136
|
+
Array(response['data']).sum { |o| o['amount'].to_d }
|
|
137
|
+
end
|
|
27
138
|
end
|
|
28
139
|
end
|
|
29
140
|
end
|
data/lib/mixin_bot/api/auth.rb
CHANGED
|
@@ -3,8 +3,22 @@
|
|
|
3
3
|
module MixinBot
|
|
4
4
|
class API
|
|
5
5
|
module Auth
|
|
6
|
+
def sign_oauth_access_token(_authorization_id:, method:, uri:, body:, scope:, request_id: nil, **kwargs)
|
|
7
|
+
MixinBot.utils.access_token(
|
|
8
|
+
method,
|
|
9
|
+
uri,
|
|
10
|
+
body,
|
|
11
|
+
exp_in: kwargs[:exp_in] || 600,
|
|
12
|
+
scp: scope,
|
|
13
|
+
app_id: kwargs[:app_id] || config.app_id,
|
|
14
|
+
session_id: kwargs[:session_id] || config.session_id,
|
|
15
|
+
private_key: kwargs[:private_key] || config.session_private_key,
|
|
16
|
+
request_id:
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
6
20
|
def oauth_token(code)
|
|
7
|
-
path = 'oauth/token'
|
|
21
|
+
path = '/oauth/token'
|
|
8
22
|
payload = {
|
|
9
23
|
client_id: config.app_id,
|
|
10
24
|
client_secret: config.client_secret,
|
|
@@ -30,20 +44,15 @@ module MixinBot
|
|
|
30
44
|
|
|
31
45
|
path = '/oauth/authorize'
|
|
32
46
|
pin = kwargs[:pin] || config.pin
|
|
47
|
+
raise ArgumentError, 'pin is required' if pin.blank?
|
|
48
|
+
|
|
49
|
+
tip = tip_or_legacy_pin_payload(pin, 'TIP:OAUTH:APPROVE:', data['scopes'], data['authorization_id'])
|
|
33
50
|
payload = {
|
|
34
51
|
authorization_id: data['authorization_id'],
|
|
35
52
|
scopes: data['scopes'],
|
|
36
|
-
pin_base64:
|
|
53
|
+
pin_base64: tip[:pin_base64] || tip[:pin]
|
|
37
54
|
}
|
|
38
55
|
|
|
39
|
-
raise ArgumentError, 'pin is required' if pin.blank?
|
|
40
|
-
|
|
41
|
-
payload[:pin_base64] = if pin.size > 6
|
|
42
|
-
encrypt_tip_pin(pin, 'TIP:OAUTH:APPROVE:', data['scopes'], data['authorization_id'])
|
|
43
|
-
else
|
|
44
|
-
encrypt_pin(pin)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
56
|
client.post path, **payload, access_token: kwargs[:access_token]
|
|
48
57
|
end
|
|
49
58
|
|
data/lib/mixin_bot/api/blaze.rb
CHANGED
|
@@ -58,6 +58,87 @@ module MixinBot
|
|
|
58
58
|
start_blaze_connect(&_block) if reconnect
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
|
+
|
|
62
|
+
def blaze_send_plain_text(socket, conversation_id:, recipient_id:, content:)
|
|
63
|
+
socket.send write_ws_message(
|
|
64
|
+
params: {
|
|
65
|
+
conversation_id:,
|
|
66
|
+
recipient_id:,
|
|
67
|
+
message_id: SecureRandom.uuid,
|
|
68
|
+
category: 'PLAIN_TEXT',
|
|
69
|
+
data_base64: Base64.urlsafe_encode64(content.to_s, padding: false)
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def blaze_send_recall_message(socket, conversation_id:, recipient_id:, message_id:)
|
|
75
|
+
data = { message_id: }.to_json
|
|
76
|
+
socket.send write_ws_message(
|
|
77
|
+
params: {
|
|
78
|
+
conversation_id:,
|
|
79
|
+
recipient_id:,
|
|
80
|
+
message_id: SecureRandom.uuid,
|
|
81
|
+
category: 'MESSAGE_RECALL',
|
|
82
|
+
data_base64: Base64.urlsafe_encode64(data, padding: false)
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def blaze_send_post(socket, conversation_id:, recipient_id:, content:)
|
|
88
|
+
blaze_send_plain_text(socket, conversation_id:, recipient_id:, content:)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def blaze_send_contact(socket, conversation_id:, recipient_id:, contact_id:)
|
|
92
|
+
data = { user_id: contact_id }.to_json
|
|
93
|
+
socket.send write_ws_message(
|
|
94
|
+
params: {
|
|
95
|
+
conversation_id:,
|
|
96
|
+
recipient_id:,
|
|
97
|
+
message_id: SecureRandom.uuid,
|
|
98
|
+
category: 'PLAIN_CONTACT',
|
|
99
|
+
data_base64: Base64.urlsafe_encode64(data, padding: false)
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def blaze_send_app_card(socket, conversation_id:, recipient_id:, title:, description:, action:, icon_url:)
|
|
105
|
+
data = { title:, description:, action:, icon_url: }.to_json
|
|
106
|
+
socket.send write_ws_message(
|
|
107
|
+
params: {
|
|
108
|
+
conversation_id:,
|
|
109
|
+
recipient_id:,
|
|
110
|
+
message_id: SecureRandom.uuid,
|
|
111
|
+
category: 'APP_CARD',
|
|
112
|
+
data_base64: Base64.urlsafe_encode64(data, padding: false)
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def blaze_send_app_button(socket, conversation_id:, recipient_id:, label:, action:, color:)
|
|
118
|
+
data = [{ label:, action:, color: }].to_json
|
|
119
|
+
socket.send write_ws_message(
|
|
120
|
+
params: {
|
|
121
|
+
conversation_id:,
|
|
122
|
+
recipient_id:,
|
|
123
|
+
message_id: SecureRandom.uuid,
|
|
124
|
+
category: 'APP_BUTTON_GROUP',
|
|
125
|
+
data_base64: Base64.urlsafe_encode64(data, padding: false)
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def blaze_send_group_app_button(socket, conversation_id:, recipient_id:, buttons:)
|
|
131
|
+
data = buttons.to_json
|
|
132
|
+
socket.send write_ws_message(
|
|
133
|
+
params: {
|
|
134
|
+
conversation_id:,
|
|
135
|
+
recipient_id:,
|
|
136
|
+
message_id: SecureRandom.uuid,
|
|
137
|
+
category: 'APP_BUTTON_GROUP',
|
|
138
|
+
data_base64: Base64.urlsafe_encode64(data, padding: false)
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
end
|
|
61
142
|
end
|
|
62
143
|
end
|
|
63
144
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MixinBot
|
|
4
|
+
class API
|
|
5
|
+
module Chain
|
|
6
|
+
CHAIN_NAMES = {
|
|
7
|
+
'59c09123-95cc-3ffd-a659-0f9169074cee' => 'Lightning',
|
|
8
|
+
'c6d0c728-2624-429b-8e0d-d9d19b6592fa' => 'Bitcoin',
|
|
9
|
+
'fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0' => 'Bitcoin Cash',
|
|
10
|
+
'574388fd-b93f-4034-a682-01c2bc095d17' => 'Bitcoin SV',
|
|
11
|
+
'76c802a2-7c88-447f-a93e-c29c9e5dd9c8' => 'Litecoin',
|
|
12
|
+
'43d61dcd-e413-450d-80b8-101d5e903357' => 'Ethereum',
|
|
13
|
+
'2204c1ee-0ea2-4add-bb9a-b3719cfff93a' => 'Ethereum Classic',
|
|
14
|
+
'1949e683-6a08-49e2-b087-d6b72398588f' => 'BNB Smart Chain',
|
|
15
|
+
'b7938396-3f94-4e0a-9179-d3440718156f' => 'Polygon',
|
|
16
|
+
'3fb612c5-6844-3979-ae4a-5a84e79da870' => 'Base',
|
|
17
|
+
'60360611-370c-3b69-9826-b13db93f6aba' => 'OP Mainnet',
|
|
18
|
+
'8c590110-1abc-3697-84f2-05214e6516aa' => 'Arbitrum One',
|
|
19
|
+
'a0ffd769-5850-4b48-9651-d2ae44a3e64d' => 'Mixin Virtual Machine',
|
|
20
|
+
'8f5caf2a-283d-4c85-832a-91e83bbf290b' => 'Decred',
|
|
21
|
+
'23dfb5a5-5d7b-48b6-905f-3970e3176e27' => 'Ripple',
|
|
22
|
+
'990c4c29-57e9-48f6-9819-7d986ea44985' => 'Siacoin',
|
|
23
|
+
'6cfe566e-4aad-470b-8c9a-2fd35b49c68d' => 'EOS',
|
|
24
|
+
'6770a1e5-6086-44d5-b60f-545f9d9e8ffd' => 'Dogecoin',
|
|
25
|
+
'6472e7e3-75fd-48b6-b1dc-28d294ee1476' => 'Dash',
|
|
26
|
+
'c996abc9-d94e-4494-b1cf-2a3fd3ac5714' => 'Zcash',
|
|
27
|
+
'27921032-f73e-434e-955f-43d55672ee31' => 'NEM',
|
|
28
|
+
'882eb041-64ea-465f-a4da-817bd3020f52' => 'Arweave',
|
|
29
|
+
'a2c5d22b-62a2-4c13-b3f0-013290dbac60' => 'Horizen',
|
|
30
|
+
'25dabac5-056a-48ff-b9f9-f67395dc407c' => 'TRON',
|
|
31
|
+
'56e63c06-b506-4ec5-885a-4a5ac17b83c1' => 'Stellar',
|
|
32
|
+
'b207bce9-c248-4b8e-b6e3-e357146f3f4c' => 'MassGrid',
|
|
33
|
+
'443e1ef5-bc9b-47d3-be77-07f328876c50' => 'Bytom',
|
|
34
|
+
'71a0e8b5-a289-4845-b661-2b70ff9968aa' => 'Bytom',
|
|
35
|
+
'7397e9f1-4e42-4dc8-8a3b-171daaadd436' => 'Cosmos',
|
|
36
|
+
'9c612618-ca59-4583-af34-be9482f5002d' => 'Akash',
|
|
37
|
+
'17f78d7c-ed96-40ff-980c-5dc62fecbc85' => 'BNB Beacon Chain',
|
|
38
|
+
'05c5ac01-31f9-4a69-aa8a-ab796de1d041' => 'Monero',
|
|
39
|
+
'c99a3779-93df-404d-945d-eddc440aa0b2' => 'Starcoin',
|
|
40
|
+
'05891083-63d2-4f3d-bfbe-d14d7fb9b25a' => 'Bitshares',
|
|
41
|
+
'6877d485-6b64-4225-8d7e-7333393cb243' => 'Ravencoin',
|
|
42
|
+
'1351e6bd-66cf-40c1-8105-8a8fe518a222' => 'Grin',
|
|
43
|
+
'c3b9153a-7fab-4138-a3a4-99849cadc073' => 'VCash',
|
|
44
|
+
'13036886-6b83-4ced-8d44-9f69151587bf' => 'Handshake',
|
|
45
|
+
'd243386e-6d84-42e6-be03-175be17bf275' => 'Nervos',
|
|
46
|
+
'5649ca42-eb5f-4c0e-ae28-d9a4e77eded3' => 'Tezos',
|
|
47
|
+
'f8b77dc0-46fd-4ea1-9821-587342475869' => 'Namecoin',
|
|
48
|
+
'64692c23-8971-4cf4-84a7-4dd1271dd887' => 'Solana',
|
|
49
|
+
'd6ac94f7-c932-4e11-97dd-617867f0669e' => 'NEAR',
|
|
50
|
+
'08285081-e1d8-4be6-9edc-e203afa932da' => 'Filecoin',
|
|
51
|
+
'eea900a8-b327-488c-8d8d-1428702fe240' => 'MobileCoin',
|
|
52
|
+
'54c61a72-b982-4034-a556-0d99e3c21e39' => 'Polkadot',
|
|
53
|
+
'9d29e4f6-d67c-4c4b-9525-604b04afbe9f' => 'Kusama',
|
|
54
|
+
'706b6f84-3333-4e55-8e89-275e71ce9803' => 'Algorand',
|
|
55
|
+
'cbc77539-0a20-4666-8c8a-4ded62b36f0a' => 'Avalanche X-Chain',
|
|
56
|
+
'1f67ac58-87ba-3571-9781-e9413c046f34' => 'Avalanche C-Chain',
|
|
57
|
+
'163a2142-398d-3483-aee3-d47db8da4d10' => 'MarsChain',
|
|
58
|
+
'b12bb04a-1cea-401c-a086-0be61f544889' => 'XDC Network',
|
|
59
|
+
'd2c1c7e1-a1a9-4f88-b282-d93b0a08b42b' => 'Aptos',
|
|
60
|
+
'2bd97283-2582-33a8-bcba-f4b8ed189572' => 'Sui',
|
|
61
|
+
'ef660437-d915-4e27-ad3f-632bfb6ba0ee' => 'TON'
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
XIN_ASSET_ID = 'c94ac88f-4671-3976-b60a-09064f1811e8'
|
|
65
|
+
VAULTA_ASSET_ID = 'ac2b79f3-ec9c-3d87-b4ca-3e825228dda5'
|
|
66
|
+
|
|
67
|
+
def network_chain(chain_id)
|
|
68
|
+
path = format('/network/chains/%<chain_id>s', chain_id:)
|
|
69
|
+
client.get path, access_token: ''
|
|
70
|
+
end
|
|
71
|
+
alias read_network_chain_by_id network_chain
|
|
72
|
+
|
|
73
|
+
def network_chains
|
|
74
|
+
client.get '/network/chains', access_token: ''
|
|
75
|
+
end
|
|
76
|
+
alias read_network_chains network_chains
|
|
77
|
+
|
|
78
|
+
def chain_name(chain_id)
|
|
79
|
+
CHAIN_NAMES[chain_id] || 'Not Supported Chain'
|
|
80
|
+
end
|
|
81
|
+
alias get_chain_name chain_name
|
|
82
|
+
|
|
83
|
+
def chain_id?(chain_id)
|
|
84
|
+
CHAIN_NAMES.key?(chain_id)
|
|
85
|
+
end
|
|
86
|
+
alias is_chain_id chain_id?
|
|
87
|
+
|
|
88
|
+
def full_chains
|
|
89
|
+
CHAIN_NAMES.transform_values { true }.dup
|
|
90
|
+
end
|
|
91
|
+
alias get_full_chains full_chains
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MixinBot
|
|
4
|
+
class API
|
|
5
|
+
module Code
|
|
6
|
+
def read_code(code_id, access_token: nil)
|
|
7
|
+
path = format('/codes/%<code_id>s', code_id:)
|
|
8
|
+
client.get path, access_token: access_token || ''
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def read_multisig_by_code(code_id, access_token: nil)
|
|
12
|
+
read_code(code_id, access_token:)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MixinBot
|
|
4
|
+
class API
|
|
5
|
+
# Delegates to {MixinBot::Computer} for API surface parity with Go SDK.
|
|
6
|
+
module ComputerApi
|
|
7
|
+
def get_computer_info
|
|
8
|
+
MixinBot::Computer.info
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get_computer_user(addr)
|
|
12
|
+
MixinBot::Computer.user(addr)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_computer_deployed_assets
|
|
16
|
+
MixinBot::Computer.deployed_assets
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_computer_system_call(id)
|
|
20
|
+
MixinBot::Computer.system_call(id)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def computer_deploy_external_asset(assets)
|
|
24
|
+
MixinBot::Computer.deploy_external_assets(assets)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def lock_computer_nonce_account(mix)
|
|
28
|
+
MixinBot::Computer.lock_nonce_account(mix)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_fee_on_xin_based_on_sol(sol_amount)
|
|
32
|
+
MixinBot::Computer.fee_on_xin_from_sol(sol_amount)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def register_computer
|
|
36
|
+
MixinBot::Computer.register_computer(self)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def computer_user_id_to_bytes(id)
|
|
40
|
+
MixinBot::Computer.user_id_to_bytes(id)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build_system_call_extra(uid, cid, skip_process: false, fid: nil)
|
|
44
|
+
MixinBot::Computer.build_system_call_extra(uid, cid, skip_process:, fid:)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def encode_operation_memo(operation, extra = +'')
|
|
48
|
+
MixinBot::Computer.encode_operation_memo(operation, extra)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def encode_mtg_extra(app_id, extra)
|
|
52
|
+
MixinBot::Computer.encode_mtg_extra(app_id, extra)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def decode_computer_extra_base64(extra)
|
|
56
|
+
MixinBot::Computer.decode_computer_extra_base64(extra)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -29,7 +29,8 @@ module MixinBot
|
|
|
29
29
|
|
|
30
30
|
def create_group_conversation(user_ids:, name:, **kwargs)
|
|
31
31
|
random_id = kwargs[:random_id] || SecureRandom.uuid
|
|
32
|
-
conversation_id = kwargs[:conversation_id] || MixinBot.utils.generate_group_conversation_id(user_ids:, name:,
|
|
32
|
+
conversation_id = kwargs[:conversation_id] || MixinBot.utils.generate_group_conversation_id(user_ids:, name:,
|
|
33
|
+
owner_id: config.app_id, random_id:)
|
|
33
34
|
create_conversation(
|
|
34
35
|
announcement: kwargs[:announcement],
|
|
35
36
|
category: 'GROUP',
|
|
@@ -94,6 +95,11 @@ module MixinBot
|
|
|
94
95
|
client.post path, access_token:
|
|
95
96
|
end
|
|
96
97
|
|
|
98
|
+
def join_conversation(conversation_id, access_token: nil)
|
|
99
|
+
path = format('/conversations/%<conversation_id>s/join', conversation_id:)
|
|
100
|
+
client.post path, access_token:
|
|
101
|
+
end
|
|
102
|
+
|
|
97
103
|
def rotate_conversation(conversation_id, access_token: nil)
|
|
98
104
|
path = format('/conversations/%<id>s/rotate', id: conversation_id)
|
|
99
105
|
|
|
@@ -194,7 +194,7 @@ module MixinBot
|
|
|
194
194
|
decrypter.key = key
|
|
195
195
|
decrypter.iv = bytes[prefix_size...(prefix_size + 12)].pack('C*')
|
|
196
196
|
decrypter.auth_tag = bytes.last(16).pack('C*')
|
|
197
|
-
decrypted = decrypter.update(bytes[(prefix_size + 12)
|
|
197
|
+
decrypted = decrypter.update(bytes[(prefix_size + 12)...-16].pack('C*'))
|
|
198
198
|
decrypter.final
|
|
199
199
|
|
|
200
200
|
Base64.urlsafe_encode64 decrypted
|
|
@@ -21,8 +21,8 @@ module MixinBot
|
|
|
21
21
|
client.get path, offset:
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def collectibles(members: [], access_token: nil)
|
|
25
|
-
unspent_outputs = safe_outputs(state
|
|
24
|
+
def collectibles(members: [], access_token: nil, state: :unspent)
|
|
25
|
+
unspent_outputs = safe_outputs(state:, members:, access_token:)['data']
|
|
26
26
|
unspent_outputs.select { |output| output['inscription_hash'].present? }
|
|
27
27
|
end
|
|
28
28
|
|