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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# frozen_string_literal:
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MixinBot
|
|
4
4
|
class API
|
|
@@ -17,65 +17,56 @@ module MixinBot
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def plain_text(options)
|
|
20
|
-
options.merge
|
|
21
|
-
base_message_params(options)
|
|
20
|
+
base_message_params(options.merge(category: 'PLAIN_TEXT'))
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def plain_post(options)
|
|
25
|
-
options.merge
|
|
26
|
-
base_message_params(options)
|
|
24
|
+
base_message_params(options.merge(category: 'PLAIN_POST'))
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
def plain_image(options)
|
|
30
|
-
options.merge
|
|
31
|
-
base_message_params(options)
|
|
28
|
+
base_message_params(options.merge(category: 'PLAIN_IMAGE'))
|
|
32
29
|
end
|
|
33
30
|
|
|
34
31
|
def plain_data(options)
|
|
35
|
-
options.merge
|
|
36
|
-
base_message_params(options)
|
|
32
|
+
base_message_params(options.merge(category: 'PLAIN_DATA'))
|
|
37
33
|
end
|
|
38
34
|
|
|
39
35
|
def plain_sticker(options)
|
|
40
|
-
options.merge
|
|
41
|
-
base_message_params(options)
|
|
36
|
+
base_message_params(options.merge(category: 'PLAIN_STICKER'))
|
|
42
37
|
end
|
|
43
38
|
|
|
44
39
|
def plain_contact(options)
|
|
45
|
-
options.merge
|
|
46
|
-
base_message_params(options)
|
|
40
|
+
base_message_params(options.merge(category: 'PLAIN_CONTACT'))
|
|
47
41
|
end
|
|
48
42
|
|
|
49
43
|
def plain_audio(options)
|
|
50
|
-
options.merge
|
|
51
|
-
base_message_params(options)
|
|
44
|
+
base_message_params(options.merge(category: 'PLAIN_AUDIO'))
|
|
52
45
|
end
|
|
53
46
|
|
|
54
47
|
def plain_video(options)
|
|
55
|
-
options.merge
|
|
56
|
-
base_message_params(options)
|
|
48
|
+
base_message_params(options.merge(category: 'PLAIN_VIDEO'))
|
|
57
49
|
end
|
|
58
50
|
|
|
59
51
|
def app_card(options)
|
|
60
|
-
options.merge
|
|
61
|
-
base_message_params(options)
|
|
52
|
+
base_message_params(options.merge(category: 'APP_CARD'))
|
|
62
53
|
end
|
|
63
54
|
|
|
64
55
|
def app_button_group(options)
|
|
65
|
-
options.merge
|
|
66
|
-
base_message_params(options)
|
|
56
|
+
base_message_params(options.merge(category: 'APP_BUTTON_GROUP'))
|
|
67
57
|
end
|
|
68
58
|
|
|
69
59
|
def recall_message_params(message_id, options)
|
|
70
60
|
raise 'recipient_id is required!' if options[:recipient_id].nil?
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
base_message_params(
|
|
63
|
+
options.merge(
|
|
64
|
+
category: 'MESSAGE_RECALL',
|
|
65
|
+
data: {
|
|
66
|
+
message_id:
|
|
67
|
+
}
|
|
68
|
+
)
|
|
77
69
|
)
|
|
78
|
-
base_message_params(options)
|
|
79
70
|
end
|
|
80
71
|
|
|
81
72
|
# base format of message params
|
|
@@ -34,6 +34,25 @@ module MixinBot
|
|
|
34
34
|
|
|
35
35
|
client.get path, access_token:
|
|
36
36
|
end
|
|
37
|
+
alias fetch_safe_multisig_request safe_multisig_request
|
|
38
|
+
|
|
39
|
+
def create_multisig_raw_tx(_asset_id:, senders:, receivers:, threshold:, inputs:, amount:, trace_id:,
|
|
40
|
+
extra: '')
|
|
41
|
+
out_hint = MixinBot.utils.unique_object_id(trace_id, 'OUTPUT', '0')
|
|
42
|
+
change_hint = MixinBot.utils.unique_object_id(trace_id, 'OUTPUT', '1')
|
|
43
|
+
keys = create_safe_keys(
|
|
44
|
+
{ receivers:, index: 0, hint: out_hint },
|
|
45
|
+
{ receivers: senders, index: 1, hint: change_hint }
|
|
46
|
+
)['data']
|
|
47
|
+
|
|
48
|
+
receivers_list = [
|
|
49
|
+
{ members: receivers, threshold: 1, amount: amount.to_s, ghosts: [keys[0]] },
|
|
50
|
+
{ members: senders, threshold:, amount: nil, ghosts: [keys[1]] }
|
|
51
|
+
].compact
|
|
52
|
+
|
|
53
|
+
tx = build_safe_transaction(utxos: inputs, receivers: receivers_list, extra:)
|
|
54
|
+
MixinBot.utils.encode_raw_transaction(tx)
|
|
55
|
+
end
|
|
37
56
|
end
|
|
38
57
|
end
|
|
39
58
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MixinBot
|
|
4
|
+
class API
|
|
5
|
+
module Network
|
|
6
|
+
def network_assets
|
|
7
|
+
client.get '/network', access_token: ''
|
|
8
|
+
end
|
|
9
|
+
alias read_network_assets network_assets
|
|
10
|
+
|
|
11
|
+
def network_assets_top
|
|
12
|
+
client.get '/network/assets/top', access_token: ''
|
|
13
|
+
end
|
|
14
|
+
alias read_network_assets_top network_assets_top
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
|
|
5
|
+
module MixinBot
|
|
6
|
+
class API
|
|
7
|
+
module NetworkAsset
|
|
8
|
+
def network_asset(asset_id)
|
|
9
|
+
path = format('/network/assets/%<asset_id>s', asset_id:)
|
|
10
|
+
client.get path, access_token: ''
|
|
11
|
+
end
|
|
12
|
+
alias read_asset network_asset
|
|
13
|
+
|
|
14
|
+
def network_ticker(asset_id, offset: nil, access_token: nil)
|
|
15
|
+
params = { asset: asset_id, offset: }.compact
|
|
16
|
+
client.get '/network/ticker', **params, access_token: access_token || ''
|
|
17
|
+
end
|
|
18
|
+
alias read_asset_ticker network_ticker
|
|
19
|
+
|
|
20
|
+
def network_asset_search(name)
|
|
21
|
+
path = "/network/assets/search/#{CGI.escape(name.to_s)}"
|
|
22
|
+
client.get path, access_token: ''
|
|
23
|
+
end
|
|
24
|
+
alias asset_search network_asset_search
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/mixin_bot/api/output.rb
CHANGED
|
@@ -21,7 +21,7 @@ module MixinBot
|
|
|
21
21
|
members = kwargs[:members].presence || [config.app_id]
|
|
22
22
|
threshold = kwargs[:threshold] || members.length
|
|
23
23
|
|
|
24
|
-
members_hash =
|
|
24
|
+
members_hash = MixinBot.utils.hash_members(members)
|
|
25
25
|
|
|
26
26
|
path = '/safe/outputs'
|
|
27
27
|
params = {
|
data/lib/mixin_bot/api/pin.rb
CHANGED
|
@@ -21,12 +21,13 @@ module MixinBot
|
|
|
21
21
|
}
|
|
22
22
|
else
|
|
23
23
|
{
|
|
24
|
-
pin:
|
|
24
|
+
pin: encrypt_pin(pin)
|
|
25
25
|
}
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
client.post path, **payload
|
|
29
29
|
end
|
|
30
|
+
alias verify_pin_tip verify_pin
|
|
30
31
|
|
|
31
32
|
# https://developers.mixin.one/api/alpha-mixin-network/create-pin/
|
|
32
33
|
def update_pin(pin:, old_pin: nil)
|
|
@@ -45,6 +46,17 @@ module MixinBot
|
|
|
45
46
|
client.post path, **payload
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
def update_tip_pin(pin, pub_tip)
|
|
50
|
+
old_encrypted = encrypt_pin(pin, iterator: (Time.now.utc.to_f * 1e9).to_i)
|
|
51
|
+
pub_buf = [pub_tip].pack('H*')
|
|
52
|
+
raise ArgumentError, 'invalid public key' unless pub_buf.bytesize == 32
|
|
53
|
+
|
|
54
|
+
pub_tip_buf = pub_buf + [1].pack('Q>')
|
|
55
|
+
encrypted_pin = encrypt_pin(pub_tip_buf.unpack1('H*'), iterator: (Time.now.utc.to_f * 1e9).to_i + 1)
|
|
56
|
+
path = '/pin/update'
|
|
57
|
+
client.post path, old_pin_base64: old_encrypted, pin_base64: encrypted_pin
|
|
58
|
+
end
|
|
59
|
+
|
|
48
60
|
def prepare_tip_key(counter = 0)
|
|
49
61
|
ed25519_key = JOSE::JWA::Ed25519.keypair
|
|
50
62
|
|
|
@@ -57,8 +69,9 @@ module MixinBot
|
|
|
57
69
|
}
|
|
58
70
|
end
|
|
59
71
|
|
|
60
|
-
def encrypt_pin(pin, iterator: nil)
|
|
61
|
-
|
|
72
|
+
def encrypt_pin(pin, iterator: nil, shared_key: nil)
|
|
73
|
+
sk = shared_key.presence || generate_shared_key_with_server
|
|
74
|
+
MixinBot.utils.encrypt_pin(pin, iterator:, shared_key: sk)
|
|
62
75
|
end
|
|
63
76
|
|
|
64
77
|
def decrypt_pin(msg)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MixinBot
|
|
4
|
+
class API
|
|
5
|
+
##
|
|
6
|
+
# Shared helpers for legacy 6-digit PIN vs TIP (+pin_base64+) payloads.
|
|
7
|
+
#
|
|
8
|
+
module PinPayload
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# @return [Hash] either +{ pin: ... }+ or +{ pin_base64: ... }+
|
|
13
|
+
#
|
|
14
|
+
def tip_or_legacy_pin_payload(pin, tip_action, *tip_params)
|
|
15
|
+
p = pin
|
|
16
|
+
raise ArgumentError, 'pin is required' if p.blank?
|
|
17
|
+
|
|
18
|
+
if p.to_s.length > 6
|
|
19
|
+
{ pin_base64: encrypt_tip_pin(p, tip_action, *tip_params) }
|
|
20
|
+
else
|
|
21
|
+
{ pin: encrypt_pin(p) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MixinBot
|
|
4
|
+
class API
|
|
5
|
+
module Session
|
|
6
|
+
def fetch_user_sessions(user_ids, access_token: nil)
|
|
7
|
+
raise ArgumentError, 'user_ids required' if user_ids.blank?
|
|
8
|
+
|
|
9
|
+
client.fetch_post_array '/sessions/fetch', Array(user_ids), access_token:
|
|
10
|
+
end
|
|
11
|
+
alias fetch_user_session fetch_user_sessions
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -17,6 +17,12 @@ module MixinBot
|
|
|
17
17
|
client.get path, **params
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def safe_snapshot(snapshot_id, access_token: nil)
|
|
21
|
+
path = format('/safe/snapshots/%<snapshot_id>s', snapshot_id:)
|
|
22
|
+
client.get path, access_token:
|
|
23
|
+
end
|
|
24
|
+
alias safe_snapshot_by_id safe_snapshot
|
|
25
|
+
|
|
20
26
|
def create_safe_snapshot_notification(**kwargs)
|
|
21
27
|
path = '/safe/snapshots/notifications'
|
|
22
28
|
|
data/lib/mixin_bot/api/tip.rb
CHANGED
|
@@ -17,6 +17,7 @@ module MixinBot
|
|
|
17
17
|
TIP:COLLECTIBLE:REQUEST:SIGN:
|
|
18
18
|
TIP:COLLECTIBLE:REQUEST:UNLOCK:
|
|
19
19
|
TIP:TRANSFER:CREATE:
|
|
20
|
+
TIP:WITHDRAW:
|
|
20
21
|
TIP:WITHDRAWAL:CREATE:
|
|
21
22
|
TIP:TRANSACTION:CREATE:
|
|
22
23
|
TIP:OAUTH:APPROVE:
|
|
@@ -30,7 +31,7 @@ module MixinBot
|
|
|
30
31
|
|
|
31
32
|
pin_key = MixinBot.utils.decode_key pin
|
|
32
33
|
|
|
33
|
-
msg = action + params.
|
|
34
|
+
msg = action + params.join
|
|
34
35
|
|
|
35
36
|
msg = Digest::SHA256.digest(msg) unless action == 'TIP:VERIFY:'
|
|
36
37
|
|
|
@@ -38,6 +39,78 @@ module MixinBot
|
|
|
38
39
|
|
|
39
40
|
encrypt_pin signature
|
|
40
41
|
end
|
|
42
|
+
|
|
43
|
+
def get_tip_node(path, request_id: nil, access_token: nil)
|
|
44
|
+
url = format('/external/tip/%<path>s', path:)
|
|
45
|
+
if request_id.present?
|
|
46
|
+
client.fetch_get url, access_token: access_token || ''
|
|
47
|
+
else
|
|
48
|
+
client.get url, access_token: access_token || ''
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
alias get_tip_node_by_path get_tip_node
|
|
52
|
+
|
|
53
|
+
def tip_migrate_body(pub_hex)
|
|
54
|
+
"TIP:MIGRATE:#{pub_hex}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def tip_body_for_verify(timestamp = (Time.now.to_f * 1e9).to_i)
|
|
58
|
+
format('TIP:VERIFY:%032d', timestamp)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tip_body_for_raw_transaction_create(asset_id, opponent_key, opponent_receivers, opponent_threshold, amount,
|
|
62
|
+
trace_id, memo)
|
|
63
|
+
receivers = Array(opponent_receivers).join
|
|
64
|
+
format(
|
|
65
|
+
'TIP:TRANSACTION:CREATE:%<asset_id>s%<opponent_key>s%<receivers>s%<opponent_threshold>s%<amount>s%<trace_id>s%<memo>s',
|
|
66
|
+
asset_id:, opponent_key:, receivers:, opponent_threshold:, amount:, trace_id:, memo:
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def tip_body_for_withdrawal_create(address_id, amount, fee, trace_id, memo)
|
|
71
|
+
format(
|
|
72
|
+
'TIP:WITHDRAWAL:CREATE:%<address_id>s%<amount>s%<fee>s%<trace_id>s%<memo>s',
|
|
73
|
+
address_id:, amount:, fee:, trace_id:, memo:
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def tip_body_for_transfer(asset_id, counter_user_id, amount, trace_id, memo)
|
|
78
|
+
format(
|
|
79
|
+
'TIP:TRANSFER:CREATE:%<asset_id>s%<counter_user_id>s%<amount>s%<trace_id>s%<memo>s',
|
|
80
|
+
asset_id:, counter_user_id:, amount:, trace_id:, memo:
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def tip_body_for_phone_number_update(verification_id, code)
|
|
85
|
+
format('TIP:PHONE:NUMBER:UPDATE:%<verification_id>s%<code>s', verification_id:, code:)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def tip_body_for_emergency_contact_create(verification_id, code)
|
|
89
|
+
format('TIP:EMERGENCY:CONTACT:CREATE:%<verification_id>s%<code>s', verification_id:, code:)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def tip_body_for_address_add(asset_id, public_key, key_tag, name)
|
|
93
|
+
format(
|
|
94
|
+
'TIP:ADDRESS:ADD:%<asset_id>s%<public_key>s%<key_tag>s%<name>s',
|
|
95
|
+
asset_id:, public_key:, key_tag:, name:
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def tip_body_for_provisioning_update(device_id, secret)
|
|
100
|
+
format('TIP:PROVISIONING:UPDATE:%<device_id>s%<secret>s', device_id:, secret:)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def tip_body_for_ownership_transfer(user_id)
|
|
104
|
+
format('TIP:APP:OWNERSHIP:TRANSFER:%<user_id>s', user_id:)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def tip_body_for_sequencer_register(user_id, public_key)
|
|
108
|
+
format('SEQUENCER:REGISTER:%<user_id>s%<public_key>s', user_id:, public_key:)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def tip_body(str)
|
|
112
|
+
str.to_s.b
|
|
113
|
+
end
|
|
41
114
|
end
|
|
42
115
|
end
|
|
43
116
|
end
|
|
@@ -36,7 +36,7 @@ module MixinBot
|
|
|
36
36
|
recipients.each_with_index do |recipient, index|
|
|
37
37
|
next if recipient[:mix_address].blank?
|
|
38
38
|
|
|
39
|
-
if recipient[:members].all?(&->(m) { m.start_with? MixinBot::
|
|
39
|
+
if recipient[:members].all?(&->(m) { m.start_with? MixinBot::MAIN_ADDRESS_PREFIX })
|
|
40
40
|
key = JOSE::JWA::Ed25519.keypair
|
|
41
41
|
gk = {
|
|
42
42
|
mask: key[0].unpack1('H*'),
|
|
@@ -54,7 +54,7 @@ module MixinBot
|
|
|
54
54
|
|
|
55
55
|
ghost_keys[index] = gk.with_indifferent_access
|
|
56
56
|
|
|
57
|
-
elsif recipient[:members].none?(&->(m) { m.start_with? MixinBot::
|
|
57
|
+
elsif recipient[:members].none?(&->(m) { m.start_with? MixinBot::MAIN_ADDRESS_PREFIX })
|
|
58
58
|
uuid_recipients.push(
|
|
59
59
|
{
|
|
60
60
|
receivers: recipient[:members],
|
|
@@ -88,7 +88,12 @@ module MixinBot
|
|
|
88
88
|
# }
|
|
89
89
|
SAFE_RAW_TRANSACTION_ARGUMENTS = %i[utxos receivers].freeze
|
|
90
90
|
def build_safe_transaction(**kwargs)
|
|
91
|
-
|
|
91
|
+
unless SAFE_RAW_TRANSACTION_ARGUMENTS.all? do |param|
|
|
92
|
+
kwargs.keys.include? param
|
|
93
|
+
end
|
|
94
|
+
raise ArgumentError,
|
|
95
|
+
"#{SAFE_RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build safe transaction"
|
|
96
|
+
end
|
|
92
97
|
raise ArgumentError, 'receivers should be an array' unless kwargs[:receivers].is_a? Array
|
|
93
98
|
raise ArgumentError, 'utxos should be an array' unless kwargs[:utxos].is_a? Array
|
|
94
99
|
|
|
@@ -126,10 +131,20 @@ module MixinBot
|
|
|
126
131
|
end
|
|
127
132
|
raise ArgumentError, 'recipients too many' if recipients.size > 256
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
mixin_asset_for = lambda do |u|
|
|
135
|
+
h = u.with_indifferent_access
|
|
136
|
+
next h[:asset] if h[:asset].present?
|
|
137
|
+
|
|
138
|
+
aid = h[:asset_id]
|
|
139
|
+
raise ArgumentError, 'utxo asset_id or asset is required' if aid.blank?
|
|
140
|
+
|
|
141
|
+
SHA3::Digest::SHA256.hexdigest(aid)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
asset = mixin_asset_for.call(utxos[0])
|
|
130
145
|
inputs = []
|
|
131
146
|
utxos.each do |utxo|
|
|
132
|
-
raise ArgumentError, 'utxo asset not match' unless utxo
|
|
147
|
+
raise ArgumentError, 'utxo asset not match' unless mixin_asset_for.call(utxo) == asset
|
|
133
148
|
|
|
134
149
|
inputs << {
|
|
135
150
|
hash: utxo['transaction_hash'],
|
|
@@ -171,6 +186,17 @@ module MixinBot
|
|
|
171
186
|
}
|
|
172
187
|
end
|
|
173
188
|
|
|
189
|
+
def verify_raw_transaction(requests)
|
|
190
|
+
requests = Array(requests).map do |r|
|
|
191
|
+
r = r.with_indifferent_access
|
|
192
|
+
{ request_id: r[:request_id], raw: r[:raw] }
|
|
193
|
+
end
|
|
194
|
+
create_safe_transaction_request(requests.first[:request_id], requests.first[:raw]) if requests.one?
|
|
195
|
+
|
|
196
|
+
path = '/safe/transaction/requests'
|
|
197
|
+
client.post path, *requests
|
|
198
|
+
end
|
|
199
|
+
|
|
174
200
|
def create_safe_transaction_request(request_id, raw)
|
|
175
201
|
path = '/safe/transaction/requests'
|
|
176
202
|
payload = [{
|
|
@@ -181,25 +207,73 @@ module MixinBot
|
|
|
181
207
|
client.post path, *payload
|
|
182
208
|
end
|
|
183
209
|
|
|
184
|
-
def send_safe_transaction(request_id, raw)
|
|
210
|
+
def send_safe_transaction(request_id, raw = nil, requests: nil)
|
|
185
211
|
path = '/safe/transactions'
|
|
186
|
-
payload =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
212
|
+
payload =
|
|
213
|
+
if requests.present?
|
|
214
|
+
Array(requests).map { |r| r.with_indifferent_access.slice(:request_id, :raw) }
|
|
215
|
+
else
|
|
216
|
+
[{ request_id:, raw: }]
|
|
217
|
+
end
|
|
190
218
|
|
|
191
219
|
client.post path, *payload
|
|
192
220
|
end
|
|
221
|
+
alias send_raw_transaction send_safe_transaction
|
|
193
222
|
|
|
194
223
|
def safe_transaction(request_id, access_token: nil)
|
|
195
224
|
path = format('/safe/transactions/%<request_id>s', request_id:)
|
|
196
225
|
|
|
197
226
|
client.get path, access_token:
|
|
198
227
|
end
|
|
228
|
+
alias get_transaction_by_id safe_transaction
|
|
229
|
+
alias get_transaction_by_id_with_safe_user safe_transaction
|
|
230
|
+
|
|
231
|
+
def estimate_storage_cost(extra)
|
|
232
|
+
step = BigDecimal(EXTRA_STORAGE_PRICE_STEP)
|
|
233
|
+
len = extra.to_s.bytesize
|
|
234
|
+
steps = (len / 1024) + 1
|
|
235
|
+
step * steps
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def storage_recipient
|
|
239
|
+
MixinBot::MixAddress.from_members(
|
|
240
|
+
members: [MixinBot.utils.burning_address],
|
|
241
|
+
threshold: 64
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def create_object_storage_transaction(extra:, trace_id:, _references: nil, limit: nil, utxos: nil, **transfer_opts)
|
|
246
|
+
amount = estimate_storage_cost(extra)
|
|
247
|
+
amount = [amount, BigDecimal(limit.to_s)].max if limit.present?
|
|
248
|
+
kwargs = {
|
|
249
|
+
asset_id: XIN_ASSET_ID,
|
|
250
|
+
amount: amount.to_s('F'),
|
|
251
|
+
trace_id:,
|
|
252
|
+
memo: extra.to_s,
|
|
253
|
+
**transfer_opts
|
|
254
|
+
}
|
|
255
|
+
kwargs[:utxos] = utxos if utxos.present?
|
|
256
|
+
kwargs[:members] = [config.app_id] unless kwargs.key?(:members)
|
|
257
|
+
create_safe_transfer(**kwargs)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def request_ghost_recipients_with_trace_id(recipients, _trace_id)
|
|
261
|
+
generate_safe_keys(
|
|
262
|
+
recipients.map do |r|
|
|
263
|
+
r = r.with_indifferent_access
|
|
264
|
+
{ members: r[:members], threshold: r[:threshold], mix_address: r[:mix_address] }
|
|
265
|
+
end
|
|
266
|
+
)
|
|
267
|
+
end
|
|
199
268
|
|
|
200
269
|
SIGN_SAFE_TRANSACTION_ARGUMENTS = %i[raw utxos request spend_key].freeze
|
|
201
270
|
def sign_safe_transaction(**kwargs)
|
|
202
|
-
|
|
271
|
+
unless SIGN_SAFE_TRANSACTION_ARGUMENTS.all? do |param|
|
|
272
|
+
kwargs.keys.include? param
|
|
273
|
+
end
|
|
274
|
+
raise ArgumentError,
|
|
275
|
+
"#{SIGN_SAFE_TRANSACTION_ARGUMENTS.join(', ')} are needed for sign safe transaction"
|
|
276
|
+
end
|
|
203
277
|
|
|
204
278
|
raw = kwargs[:raw]
|
|
205
279
|
tx = MixinBot.utils.decode_raw_transaction raw
|
|
@@ -265,7 +339,12 @@ module MixinBot
|
|
|
265
339
|
|
|
266
340
|
INSCRIBE_TRANSACTION_ARGUMENTS = %i[content collection_hash].freeze
|
|
267
341
|
def build_inscribe_transaction(**kwargs)
|
|
268
|
-
|
|
342
|
+
unless INSCRIBE_TRANSACTION_ARGUMENTS.all? do |param|
|
|
343
|
+
kwargs.keys.include? param
|
|
344
|
+
end
|
|
345
|
+
raise ArgumentError,
|
|
346
|
+
"#{INSCRIBE_TRANSACTION_ARGUMENTS.join(', ')} are needed for inscribe transaction"
|
|
347
|
+
end
|
|
269
348
|
|
|
270
349
|
receivers = kwargs[:receivers].presence || [config.app_id]
|
|
271
350
|
receivers_threshold = kwargs[:receivers_threshold] || receivers.length
|
|
@@ -283,14 +362,20 @@ module MixinBot
|
|
|
283
362
|
MixinBot.api.build_object_transaction data.to_json, references: [collection_hash]
|
|
284
363
|
end
|
|
285
364
|
|
|
286
|
-
OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS = %i[amount inscription_hash utxos].freeze
|
|
365
|
+
OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS = %i[amount inscription_hash sequence utxos].freeze
|
|
287
366
|
def build_occupy_transaction(**kwargs)
|
|
288
|
-
|
|
367
|
+
unless OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS.all? do |param|
|
|
368
|
+
kwargs.keys.include? param
|
|
369
|
+
end
|
|
370
|
+
raise ArgumentError,
|
|
371
|
+
"#{OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS.join(', ')} are needed for occupy NFT transaction"
|
|
372
|
+
end
|
|
289
373
|
|
|
290
374
|
members = kwargs[:members].presence || [config.app_id]
|
|
291
375
|
threshold = kwargs[:threshold] || members.length
|
|
292
376
|
amount = kwargs[:amount]
|
|
293
377
|
inscription_hash = kwargs[:inscription_hash]
|
|
378
|
+
sequence = kwargs[:sequence]
|
|
294
379
|
|
|
295
380
|
receivers = [
|
|
296
381
|
{
|
|
@@ -302,11 +387,15 @@ module MixinBot
|
|
|
302
387
|
|
|
303
388
|
extra = {
|
|
304
389
|
operation: 'occupy',
|
|
305
|
-
|
|
306
|
-
content:
|
|
390
|
+
sequence:
|
|
307
391
|
}.to_json
|
|
308
392
|
|
|
309
|
-
|
|
393
|
+
build_safe_transaction(utxos: kwargs[:utxos], receivers:, extra:, references: [inscription_hash])
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def send_kernel_transaction_from_account(**_kwargs)
|
|
397
|
+
raise NotImplementedError,
|
|
398
|
+
'send_kernel_transaction_from_account requires kernel UTXO signing; use native mixin CLI or build manually'
|
|
310
399
|
end
|
|
311
400
|
end
|
|
312
401
|
end
|