mixin_bot 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mixin_bot/api/auth.rb +5 -5
- data/lib/mixin_bot/api/encrypted_message.rb +2 -2
- data/lib/mixin_bot/api/inscription.rb +71 -0
- data/lib/mixin_bot/api/{collectible.rb → legacy_collectible.rb} +4 -4
- data/lib/mixin_bot/api/legacy_multisig.rb +1 -1
- data/lib/mixin_bot/api/legacy_output.rb +3 -3
- data/lib/mixin_bot/api/multisig.rb +5 -5
- data/lib/mixin_bot/api/output.rb +3 -1
- data/lib/mixin_bot/api/pin.rb +1 -1
- data/lib/mixin_bot/api/rpc.rb +5 -5
- data/lib/mixin_bot/api/transaction.rb +134 -23
- data/lib/mixin_bot/api/transfer.rb +16 -10
- data/lib/mixin_bot/api/user.rb +8 -3
- data/lib/mixin_bot/api.rb +6 -4
- data/lib/mixin_bot/client.rb +1 -3
- data/lib/mixin_bot/nfo.rb +1 -1
- data/lib/mixin_bot/transaction.rb +77 -58
- data/lib/mixin_bot/utils/address.rb +15 -2
- data/lib/mixin_bot/utils/crypto.rb +61 -25
- data/lib/mixin_bot/utils/decoder.rb +4 -6
- data/lib/mixin_bot/utils/encoder.rb +11 -11
- data/lib/mixin_bot/version.rb +1 -1
- data/lib/mvm/client.rb +4 -4
- data/lib/mvm/registry.rb +2 -2
- metadata +19 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4257bfdffc20e27ffab5ce9da23080be113a552623f5022775f3cc8a6cc6db0c
|
4
|
+
data.tar.gz: 7f29a034845b45715e33b3b51e28be996408fcff145538025a243fb74d42b482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18e9d94005f235f827ce4e7598a2b2ddf13f48b296d9af46e4d6b2550e795c30f805c40b815352c159f7f2aa7b35c0a4e93ea0787889175c373e931720b5dc17
|
7
|
+
data.tar.gz: 56e0c4651199912fd4ed4bef7eb96cc22de1cd30842b7d8e767811ece926e880984a0f2734db19d4f1c48464c037e3b3d9c22b61be204c8a83ffd0f926203e37
|
data/lib/mixin_bot/api/auth.rb
CHANGED
@@ -52,8 +52,8 @@ module MixinBot
|
|
52
52
|
@_scope = scope.join(' ')
|
53
53
|
EM.run do
|
54
54
|
start_blaze_connect do
|
55
|
-
def on_open(
|
56
|
-
|
55
|
+
def on_open(websocket, _event) # rubocop:disable Lint/NestedMethodDefinition
|
56
|
+
websocket.send write_ws_message(
|
57
57
|
action: 'REFRESH_OAUTH_CODE',
|
58
58
|
params: {
|
59
59
|
client_id: @_app_id,
|
@@ -64,13 +64,13 @@ module MixinBot
|
|
64
64
|
)
|
65
65
|
end
|
66
66
|
|
67
|
-
def on_message(
|
67
|
+
def on_message(websocket, event) # rubocop:disable Lint/NestedMethodDefinition
|
68
68
|
raw = JSON.parse ws_message(event.data)
|
69
69
|
@_data = raw
|
70
|
-
|
70
|
+
websocket.close
|
71
71
|
end
|
72
72
|
|
73
|
-
def on_close(
|
73
|
+
def on_close(_websocket, _event) # rubocop:disable Lint/NestedMethodDefinition
|
74
74
|
EM.stop_event_loop
|
75
75
|
end
|
76
76
|
end
|
@@ -110,7 +110,7 @@ module MixinBot
|
|
110
110
|
client.post path, *payload
|
111
111
|
end
|
112
112
|
|
113
|
-
def encrypt_message(data, sessions = [], sk: nil, pk: nil)
|
113
|
+
def encrypt_message(data, sessions = [], sk: nil, pk: nil) # rubocop:disable Naming/MethodParameterName
|
114
114
|
raise ArgumentError, 'Wrong sessions format!' unless sessions.all?(&->(s) { s.key?('session_id') && s.key?('public_key') })
|
115
115
|
|
116
116
|
sk ||= config.session_private_key[0...32]
|
@@ -153,7 +153,7 @@ module MixinBot
|
|
153
153
|
Base64.urlsafe_encode64 bytes.pack('C*'), padding: false
|
154
154
|
end
|
155
155
|
|
156
|
-
def decrypt_message(data, sk: nil, si: nil)
|
156
|
+
def decrypt_message(data, sk: nil, si: nil) # rubocop:disable Naming/MethodParameterName
|
157
157
|
bytes = Base64.urlsafe_decode64(data).bytes
|
158
158
|
|
159
159
|
si ||= config.session_id
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module Inscription
|
6
|
+
def collection(hash)
|
7
|
+
path = "/safe/inscriptions/collections/#{hash}"
|
8
|
+
|
9
|
+
client.get path
|
10
|
+
end
|
11
|
+
|
12
|
+
def collectible(hash)
|
13
|
+
path = "/safe/inscriptions/items/#{hash}"
|
14
|
+
|
15
|
+
client.get path
|
16
|
+
end
|
17
|
+
|
18
|
+
def collectibles(members: [], access_token: nil)
|
19
|
+
unspent_outputs = safe_outputs(state: :unspent, members:, access_token:)['data']
|
20
|
+
unspent_outputs.select { |output| output['inscription_hash'].present? }
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_collectible_transfer(utxo, **kwargs)
|
24
|
+
# verify collectible
|
25
|
+
utxo = utxo.with_indifferent_access
|
26
|
+
raise MixinBot::ArgumentError, 'not a valid collectible' unless utxo['inscription_hash'].present?
|
27
|
+
|
28
|
+
# verify members
|
29
|
+
members = [kwargs[:members]].flatten.compact
|
30
|
+
raise ArgumentError, 'members required' if members.blank?
|
31
|
+
|
32
|
+
threshold = kwargs[:threshold] || members.length
|
33
|
+
request_id = kwargs[:request_id] || kwargs[:trace_id] || SecureRandom.uuid
|
34
|
+
|
35
|
+
memo = kwargs[:memo] || ''
|
36
|
+
|
37
|
+
# build transaction
|
38
|
+
tx = build_safe_transaction(
|
39
|
+
utxos: [utxo],
|
40
|
+
receivers: [{
|
41
|
+
members:,
|
42
|
+
threshold:,
|
43
|
+
amount: utxo['amount']
|
44
|
+
}],
|
45
|
+
extra: memo
|
46
|
+
)
|
47
|
+
|
48
|
+
# encode transaction
|
49
|
+
raw = MixinBot.utils.encode_raw_transaction tx
|
50
|
+
|
51
|
+
# verify transaction
|
52
|
+
request = create_safe_transaction_request(request_id, raw)['data']
|
53
|
+
|
54
|
+
# sign transaction
|
55
|
+
spend_key = MixinBot.utils.decode_key(kwargs[:spend_key]) || config.spend_key
|
56
|
+
signed_raw = MixinBot.api.sign_safe_transaction(
|
57
|
+
raw:,
|
58
|
+
utxos: [utxo],
|
59
|
+
request: request[0],
|
60
|
+
spend_key:
|
61
|
+
)
|
62
|
+
|
63
|
+
# submit transaction
|
64
|
+
send_safe_transaction(
|
65
|
+
request_id,
|
66
|
+
signed_raw
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -2,20 +2,20 @@
|
|
2
2
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
|
-
module
|
5
|
+
module LegacyCollectible
|
6
6
|
NFT_ASSET_MIXIN_ID = '1700941284a95f31b25ec8c546008f208f88eee4419ccdcdbe6e3195e60128ca'
|
7
7
|
|
8
|
-
def
|
8
|
+
def legacy_collectible(id, access_token: nil)
|
9
9
|
path = "/collectibles/tokens/#{id}"
|
10
10
|
client.get path, access_token:
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def legacy_collection(id, access_token: nil)
|
14
14
|
path = "/collectibles/collections/#{id}"
|
15
15
|
client.get path, access_token:
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
18
|
+
def legacy_collectibles(**kwargs)
|
19
19
|
limit = kwargs[:limit] || 100
|
20
20
|
offset = kwargs[:offset] || ''
|
21
21
|
state = kwargs[:state] || ''
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module LegacyOutput
|
6
|
-
def
|
6
|
+
def legacy_outputs(**kwargs)
|
7
7
|
limit = kwargs[:limit] || 100
|
8
8
|
offset = kwargs[:offset] || ''
|
9
9
|
state = kwargs[:state] || ''
|
@@ -23,8 +23,8 @@ module MixinBot
|
|
23
23
|
|
24
24
|
client.get path, **params, access_token:
|
25
25
|
end
|
26
|
-
alias multisigs
|
27
|
-
alias multisig_outputs
|
26
|
+
alias multisigs legacy_outputs
|
27
|
+
alias multisig_outputs legacy_outputs
|
28
28
|
|
29
29
|
def create_output(receivers:, index:, hint: nil, access_token: nil)
|
30
30
|
path = '/outputs'
|
@@ -6,11 +6,11 @@ module MixinBot
|
|
6
6
|
def create_safe_multisig_request(request_id, raw, access_token: nil)
|
7
7
|
path = '/safe/multisigs'
|
8
8
|
payload = [{
|
9
|
-
request_id
|
10
|
-
raw:
|
9
|
+
request_id:,
|
10
|
+
raw:
|
11
11
|
}]
|
12
12
|
|
13
|
-
client.post path, *payload
|
13
|
+
client.post path, *payload, access_token:
|
14
14
|
end
|
15
15
|
|
16
16
|
def sign_safe_multisig_request(request_id, raw, access_token: nil)
|
@@ -20,13 +20,13 @@ module MixinBot
|
|
20
20
|
raw:
|
21
21
|
}
|
22
22
|
|
23
|
-
client.post path, **payload
|
23
|
+
client.post path, **payload, access_token:
|
24
24
|
end
|
25
25
|
|
26
26
|
def unlock_safe_multisig_request(request_id, access_token: nil)
|
27
27
|
path = format('/safe/multisigs/%<request_id>s/unlock', request_id:)
|
28
28
|
|
29
|
-
client.post path, access_token:
|
29
|
+
client.post path, access_token:
|
30
30
|
end
|
31
31
|
|
32
32
|
def safe_multisig_request(request_id, access_token: nil)
|
data/lib/mixin_bot/api/output.rb
CHANGED
@@ -18,7 +18,7 @@ module MixinBot
|
|
18
18
|
state = kwargs[:state] || ''
|
19
19
|
access_token = kwargs[:access_token]
|
20
20
|
order = kwargs[:order] || 'ASC'
|
21
|
-
members = kwargs[:members] || [config.app_id]
|
21
|
+
members = kwargs[:members].presence || [config.app_id]
|
22
22
|
threshold = kwargs[:threshold] || members.length
|
23
23
|
|
24
24
|
members_hash = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
|
@@ -36,11 +36,13 @@ module MixinBot
|
|
36
36
|
|
37
37
|
client.get path, **params, access_token:
|
38
38
|
end
|
39
|
+
alias outputs safe_outputs
|
39
40
|
|
40
41
|
def safe_output(id, access_token: nil)
|
41
42
|
path = format('/safe/outputs/%<id>s', id:)
|
42
43
|
client.get path, access_token:
|
43
44
|
end
|
45
|
+
alias output safe_output
|
44
46
|
end
|
45
47
|
end
|
46
48
|
end
|
data/lib/mixin_bot/api/pin.rb
CHANGED
@@ -49,7 +49,7 @@ module MixinBot
|
|
49
49
|
ed25519_key = JOSE::JWA::Ed25519.keypair
|
50
50
|
|
51
51
|
private_key = ed25519_key[1].unpack1('H*')
|
52
|
-
public_key = (ed25519_key[0].bytes + MixinBot::Utils.
|
52
|
+
public_key = (ed25519_key[0].bytes + MixinBot::Utils.encode_uint64(counter + 1)).pack('c*').unpack1('H*')
|
53
53
|
|
54
54
|
{
|
55
55
|
private_key:,
|
data/lib/mixin_bot/api/rpc.rb
CHANGED
@@ -19,7 +19,7 @@ module MixinBot
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def get_transaction(hash, access_token: nil)
|
22
|
-
rpc_proxy('gettransaction', [hash], access_token:
|
22
|
+
rpc_proxy('gettransaction', [hash], access_token:)
|
23
23
|
end
|
24
24
|
|
25
25
|
def get_utxo(hash, index = 0, access_token: nil)
|
@@ -30,16 +30,16 @@ module MixinBot
|
|
30
30
|
rpc_proxy 'getsnapshot', [hash], access_token:
|
31
31
|
end
|
32
32
|
|
33
|
-
def list_snapshots(offset = 0, count = 10, sig
|
34
|
-
rpc_proxy 'listsnapshots', [offset, count, sig,
|
33
|
+
def list_snapshots(offset = 0, count = 10, sig: false, txn: false, access_token: nil)
|
34
|
+
rpc_proxy 'listsnapshots', [offset, count, sig, txn], access_token:
|
35
35
|
end
|
36
36
|
|
37
37
|
def list_mint_works(offset = 0, access_token: nil)
|
38
38
|
rpc_proxy 'listmintworks', [offset], access_token:
|
39
39
|
end
|
40
40
|
|
41
|
-
def list_mint_distributions(offset = 0, count = 10,
|
42
|
-
rpc_proxy 'listmintdistributions', [offset, count,
|
41
|
+
def list_mint_distributions(offset = 0, count = 10, txn: false, access_token: nil)
|
42
|
+
rpc_proxy 'listmintdistributions', [offset, count, txn], access_token:
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -6,8 +6,10 @@ module MixinBot
|
|
6
6
|
SAFE_TX_VERSION = 0x05
|
7
7
|
OUTPUT_TYPE_SCRIPT = 0x00
|
8
8
|
OUTPUT_TYPE_WITHDRAW_SUBMIT = 0xa1
|
9
|
+
XIN_ASSET_ID = 'c94ac88f-4671-3976-b60a-09064f1811e8'
|
10
|
+
EXTRA_SIZE_STORAGE_CAPACITY = 1024 * 1024 * 4
|
11
|
+
EXTRA_STORAGE_PRICE_STEP = 0.0001
|
9
12
|
|
10
|
-
# ghost keys
|
11
13
|
def create_safe_keys(*payload, access_token: nil)
|
12
14
|
raise ArgumentError, 'payload should be an array' unless payload.is_a? Array
|
13
15
|
raise ArgumentError, 'payload should not be empty' unless payload.size.positive?
|
@@ -21,10 +23,58 @@ module MixinBot
|
|
21
23
|
|
22
24
|
path = '/safe/keys'
|
23
25
|
|
24
|
-
client.post path, *payload
|
26
|
+
client.post path, *payload, access_token:
|
25
27
|
end
|
26
28
|
alias create_ghost_keys create_safe_keys
|
27
29
|
|
30
|
+
def generate_safe_keys(recipients)
|
31
|
+
raise ArgumentError, 'recipients should be an array' unless recipients.is_a? Array
|
32
|
+
|
33
|
+
ghost_keys = []
|
34
|
+
uuid_recipients = []
|
35
|
+
|
36
|
+
recipients.each_with_index do |recipient, index|
|
37
|
+
next if recipient[:mix_address].blank?
|
38
|
+
|
39
|
+
if recipient[:members].all?(&->(m) { m.start_with? MixinBot::Utils::Address::MAIN_ADDRESS_PREFIX })
|
40
|
+
key = JOSE::JWA::Ed25519.keypair
|
41
|
+
gk = {
|
42
|
+
mask: key[0].unpack1('H*'),
|
43
|
+
keys: []
|
44
|
+
}
|
45
|
+
recipient[:members].each do |member|
|
46
|
+
payload = MixinBot.utils.parse_main_address member
|
47
|
+
spend_key = payload[0...32]
|
48
|
+
view_key = payload[-32..]
|
49
|
+
|
50
|
+
ghost_public_key = MixinBot.utils.derive_ghost_public_key(key[1], view_key, spend_key, index)
|
51
|
+
|
52
|
+
gk[:keys] << ghost_public_key.unpack1('H*')
|
53
|
+
end
|
54
|
+
|
55
|
+
ghost_keys[index] = gk.with_indifferent_access
|
56
|
+
|
57
|
+
elsif recipient[:members].none?(&->(m) { m.start_with? MixinBot::Utils::Address::MAIN_ADDRESS_PREFIX })
|
58
|
+
uuid_recipients.push(
|
59
|
+
{
|
60
|
+
receivers: recipient[:members],
|
61
|
+
index:,
|
62
|
+
hint: SecureRandom.uuid
|
63
|
+
}.with_indifferent_access
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if uuid_recipients.present?
|
69
|
+
keys = create_safe_keys(*uuid_recipients)['data']
|
70
|
+
keys.each_with_index do |key, index|
|
71
|
+
ghost_keys[uuid_recipients[index][:index]] = key
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
ghost_keys
|
76
|
+
end
|
77
|
+
|
28
78
|
# kwargs:
|
29
79
|
# {
|
30
80
|
# utxos: [ utxo ],
|
@@ -39,6 +89,8 @@ module MixinBot
|
|
39
89
|
SAFE_RAW_TRANSACTION_ARGUMENTS = %i[utxos receivers].freeze
|
40
90
|
def build_safe_transaction(**kwargs)
|
41
91
|
raise ArgumentError, "#{SAFE_RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build safe transaction" unless SAFE_RAW_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
92
|
+
raise ArgumentError, 'receivers should be an array' unless kwargs[:receivers].is_a? Array
|
93
|
+
raise ArgumentError, 'utxos should be an array' unless kwargs[:utxos].is_a? Array
|
42
94
|
|
43
95
|
utxos = kwargs[:utxos].map(&:with_indifferent_access)
|
44
96
|
receivers = kwargs[:receivers].map(&:with_indifferent_access)
|
@@ -63,12 +115,12 @@ module MixinBot
|
|
63
115
|
inputs_sum = utxos.sum(&->(utxo) { utxo['amount'].to_d })
|
64
116
|
outputs_sum = recipients.sum(&->(recipient) { recipient['amount'].to_d })
|
65
117
|
change = inputs_sum - outputs_sum
|
66
|
-
raise InsufficientBalanceError, "inputs sum
|
118
|
+
raise InsufficientBalanceError, "inputs sum #{inputs_sum} < outputs sum #{outputs_sum}" if change.negative?
|
67
119
|
|
68
120
|
if change.positive?
|
69
121
|
recipients << MixinBot.utils.build_safe_recipient(
|
70
|
-
members: utxos[
|
71
|
-
threshold: utxos[
|
122
|
+
members: utxos.first['receivers'],
|
123
|
+
threshold: utxos.first['receivers_threshold'],
|
72
124
|
amount: change
|
73
125
|
).with_indifferent_access
|
74
126
|
end
|
@@ -85,14 +137,7 @@ module MixinBot
|
|
85
137
|
}
|
86
138
|
end
|
87
139
|
|
88
|
-
|
89
|
-
{
|
90
|
-
receivers: r[:members],
|
91
|
-
index:,
|
92
|
-
hint: SecureRandom.uuid
|
93
|
-
}
|
94
|
-
end
|
95
|
-
ghosts = create_safe_keys(*ghost_payload)['data']
|
140
|
+
ghosts = generate_safe_keys(recipients)
|
96
141
|
|
97
142
|
outputs = []
|
98
143
|
recipients.each_with_index do |recipient, index|
|
@@ -121,7 +166,8 @@ module MixinBot
|
|
121
166
|
asset:,
|
122
167
|
inputs:,
|
123
168
|
outputs:,
|
124
|
-
extra: kwargs[:extra] || ''
|
169
|
+
extra: kwargs[:extra] || '',
|
170
|
+
references: kwargs[:references] || []
|
125
171
|
}
|
126
172
|
end
|
127
173
|
|
@@ -164,7 +210,7 @@ module MixinBot
|
|
164
210
|
|
165
211
|
msg = [raw].pack('H*')
|
166
212
|
|
167
|
-
|
213
|
+
y_scalar = JOSE::JWA::FieldElement.new(
|
168
214
|
JOSE::JWA::X25519.clamp_scalar(spend_key[...32]).x,
|
169
215
|
JOSE::JWA::Edwards25519Point::L
|
170
216
|
)
|
@@ -175,16 +221,12 @@ module MixinBot
|
|
175
221
|
raise ArgumentError, 'utxo not match' unless input['hash'] == utxo['transaction_hash'] && input['index'] == utxo['output_index']
|
176
222
|
|
177
223
|
view = [request['views'][index]].pack('H*')
|
178
|
-
|
179
|
-
# https://github.com/potatosalad/ruby-jose/blob/e1be589b889f1e59ac233a5d19a3fa13f1e4b8a0/lib/jose/jwa/x25519.rb#L122C14-L122C48
|
180
|
-
OpenSSL::BN.new(view.reverse, 2),
|
181
|
-
JOSE::JWA::Edwards25519Point::L
|
182
|
-
)
|
224
|
+
x_scalar = MixinBot.utils.scalar_from_bytes(view)
|
183
225
|
|
184
|
-
|
185
|
-
key =
|
226
|
+
t_scalar = x_scalar + y_scalar
|
227
|
+
key = t_scalar.to_bytes(JOSE::JWA::Edwards25519Point::B)
|
186
228
|
|
187
|
-
pub = MixinBot.utils.
|
229
|
+
pub = MixinBot.utils.shared_public_key key
|
188
230
|
key_index = utxo['keys'].index pub.unpack1('H*')
|
189
231
|
raise ArgumentError, 'cannot find valid key' unless key_index.is_a? Integer
|
190
232
|
|
@@ -197,6 +239,75 @@ module MixinBot
|
|
197
239
|
|
198
240
|
MixinBot.utils.encode_raw_transaction tx
|
199
241
|
end
|
242
|
+
|
243
|
+
def build_object_transaction(extra, **)
|
244
|
+
extra = extra.to_s
|
245
|
+
raise ArgumentError, 'Extra too large' if extra.bytesize > EXTRA_SIZE_STORAGE_CAPACITY
|
246
|
+
|
247
|
+
# calculate fee base on extra length
|
248
|
+
amount = EXTRA_STORAGE_PRICE_STEP * ((extra.bytesize / 1024) + 1)
|
249
|
+
|
250
|
+
# burning address
|
251
|
+
receivers = [
|
252
|
+
{
|
253
|
+
members: [MixinBot.utils.burning_address],
|
254
|
+
threshold: 64,
|
255
|
+
amount:
|
256
|
+
}
|
257
|
+
]
|
258
|
+
|
259
|
+
# find XIN utxos
|
260
|
+
utxos = build_utxos(asset_id: XIN_ASSET_ID, amount:)
|
261
|
+
|
262
|
+
# build transaction
|
263
|
+
build_safe_transaction utxos:, receivers:, extra:, **
|
264
|
+
end
|
265
|
+
|
266
|
+
INSCRIBE_TRANSACTION_ARGUMENTS = %i[content collection_hash].freeze
|
267
|
+
def build_inscribe_transaction(**kwargs)
|
268
|
+
raise ArgumentError, "#{INSCRIBE_TRANSACTION_ARGUMENTS.join(', ')} are needed for inscribe transaction" unless INSCRIBE_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
269
|
+
|
270
|
+
receivers = kwargs[:receivers].presence || [config.app_id]
|
271
|
+
receivers_threshold = kwargs[:receivers_threshold] || receivers.length
|
272
|
+
recipient = MixinBot.utils.build_mix_address(receivers, receivers_threshold)
|
273
|
+
|
274
|
+
content = kwargs[:content]
|
275
|
+
collection_hash = kwargs[:collection_hash]
|
276
|
+
|
277
|
+
data = {
|
278
|
+
operation: 'inscribe',
|
279
|
+
recipient:,
|
280
|
+
content:
|
281
|
+
}
|
282
|
+
|
283
|
+
MixinBot.api.build_object_transaction data.to_json, references: [collection_hash]
|
284
|
+
end
|
285
|
+
|
286
|
+
OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS = %i[amount inscription_hash utxos].freeze
|
287
|
+
def build_occupy_transaction(**kwargs)
|
288
|
+
raise ArgumentError, "#{OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS.join(', ')} are needed for occupy NFT transaction" unless OCCUPY_INSCRIPTION_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
289
|
+
|
290
|
+
members = kwargs[:members].presence || [config.app_id]
|
291
|
+
threshold = kwargs[:threshold] || members.length
|
292
|
+
amount = kwargs[:amount]
|
293
|
+
inscription_hash = kwargs[:inscription_hash]
|
294
|
+
|
295
|
+
receivers = [
|
296
|
+
{
|
297
|
+
members:,
|
298
|
+
threshold:,
|
299
|
+
amount:
|
300
|
+
}
|
301
|
+
]
|
302
|
+
|
303
|
+
extra = {
|
304
|
+
operation: 'occupy',
|
305
|
+
recipient:,
|
306
|
+
content:
|
307
|
+
}.to_json
|
308
|
+
|
309
|
+
MixinBot.api.build_safe_transaction(utxos:, receivers:, extra:, references: [inscription_hash])
|
310
|
+
end
|
200
311
|
end
|
201
312
|
end
|
202
313
|
end
|
@@ -29,15 +29,7 @@ module MixinBot
|
|
29
29
|
memo = kwargs[:memo] || ''
|
30
30
|
|
31
31
|
# step 1: select inputs
|
32
|
-
|
33
|
-
|
34
|
-
utxos = []
|
35
|
-
outputs.each do |output|
|
36
|
-
break if utxos.sum { |o| o['amount'].to_d } >= amount
|
37
|
-
|
38
|
-
utxos.shift if utxos.size >= 256
|
39
|
-
utxos << output
|
40
|
-
end
|
32
|
+
utxos = build_utxos(asset_id:, amount:)
|
41
33
|
|
42
34
|
# step 2: build transaction
|
43
35
|
tx = build_safe_transaction(
|
@@ -56,7 +48,7 @@ module MixinBot
|
|
56
48
|
|
57
49
|
# step 4: sign transaction
|
58
50
|
spend_key = MixinBot.utils.decode_key(kwargs[:spend_key]) || config.spend_key
|
59
|
-
signed_raw = sign_safe_transaction(
|
51
|
+
signed_raw = MixinBot.api.sign_safe_transaction(
|
60
52
|
raw:,
|
61
53
|
utxos:,
|
62
54
|
request: request[0],
|
@@ -69,6 +61,20 @@ module MixinBot
|
|
69
61
|
signed_raw
|
70
62
|
)
|
71
63
|
end
|
64
|
+
|
65
|
+
def build_utxos(asset_id:, amount:)
|
66
|
+
outputs = safe_outputs(state: 'unspent', asset: asset_id, limit: 500)['data'].sort_by { |o| o['amount'].to_d }
|
67
|
+
|
68
|
+
utxos = []
|
69
|
+
outputs.each do |output|
|
70
|
+
break if utxos.sum { |o| o['amount'].to_d } >= amount
|
71
|
+
|
72
|
+
utxos.shift if utxos.size >= 256
|
73
|
+
utxos << output
|
74
|
+
end
|
75
|
+
|
76
|
+
utxos
|
77
|
+
end
|
72
78
|
end
|
73
79
|
end
|
74
80
|
end
|
data/lib/mixin_bot/api/user.rb
CHANGED
@@ -86,19 +86,24 @@ module MixinBot
|
|
86
86
|
client.post path, **payload
|
87
87
|
end
|
88
88
|
|
89
|
-
def migrate_to_safe(spend_key:,
|
89
|
+
def migrate_to_safe(spend_key:, pin: nil)
|
90
90
|
profile = me['data']
|
91
91
|
return true if profile['has_safe']
|
92
92
|
|
93
93
|
spend_keypair = JOSE::JWA::Ed25519.keypair spend_key
|
94
94
|
spend_key = spend_keypair[1].unpack1('H*')
|
95
95
|
|
96
|
-
|
96
|
+
if profile['tip_key_base64'].blank?
|
97
|
+
new_pin = MixinBot.utils.tip_public_key(spend_keypair[0], counter: profile['tip_counter'])
|
98
|
+
update_pin(pin: new_pin, old_pin: pin)
|
99
|
+
|
100
|
+
pin = new_pin
|
101
|
+
end
|
97
102
|
|
98
103
|
# wait for tip pin update in server
|
99
104
|
sleep 1
|
100
105
|
|
101
|
-
safe_register spend_key
|
106
|
+
safe_register pin, spend_key
|
102
107
|
|
103
108
|
{
|
104
109
|
spend_key:
|
data/lib/mixin_bot/api.rb
CHANGED
@@ -8,9 +8,10 @@ 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/collectible'
|
12
11
|
require_relative 'api/conversation'
|
13
12
|
require_relative 'api/encrypted_message'
|
13
|
+
require_relative 'api/inscription'
|
14
|
+
require_relative 'api/legacy_collectible'
|
14
15
|
require_relative 'api/legacy_multisig'
|
15
16
|
require_relative 'api/legacy_output'
|
16
17
|
require_relative 'api/legacy_payment'
|
@@ -67,8 +68,8 @@ module MixinBot
|
|
67
68
|
)
|
68
69
|
end
|
69
70
|
|
70
|
-
def encode_raw_transaction(
|
71
|
-
utils.encode_raw_transaction
|
71
|
+
def encode_raw_transaction(txn)
|
72
|
+
utils.encode_raw_transaction txn
|
72
73
|
end
|
73
74
|
|
74
75
|
def decode_raw_transaction(raw)
|
@@ -107,9 +108,10 @@ module MixinBot
|
|
107
108
|
include MixinBot::API::Attachment
|
108
109
|
include MixinBot::API::Auth
|
109
110
|
include MixinBot::API::Blaze
|
110
|
-
include MixinBot::API::Collectible
|
111
111
|
include MixinBot::API::Conversation
|
112
112
|
include MixinBot::API::EncryptedMessage
|
113
|
+
include MixinBot::API::Inscription
|
114
|
+
include MixinBot::API::LegacyCollectible
|
113
115
|
include MixinBot::API::LegacyMultisig
|
114
116
|
include MixinBot::API::LegacyOutput
|
115
117
|
include MixinBot::API::LegacyPayment
|
data/lib/mixin_bot/client.rb
CHANGED
@@ -78,7 +78,7 @@ module MixinBot
|
|
78
78
|
return result
|
79
79
|
end
|
80
80
|
|
81
|
-
errmsg = "#{verb.upcase}
|
81
|
+
errmsg = "#{verb.upcase} | #{path} | #{body}, errcode: #{result['error']['code']}, errmsg: #{result['error']['description']}, request_id: #{response&.[]('X-Request-Id')}, server_time: #{response&.[]('X-Server-Time')}'"
|
82
82
|
|
83
83
|
case result['error']['code']
|
84
84
|
when 401, 20121
|
@@ -87,8 +87,6 @@ module MixinBot
|
|
87
87
|
raise ForbiddenError, errmsg
|
88
88
|
when 404
|
89
89
|
raise NotFoundError, errmsg
|
90
|
-
when 400, 10006, 20133, 500, 7000, 7001
|
91
|
-
raise ResponseError, errmsg
|
92
90
|
when 20117
|
93
91
|
raise InsufficientBalanceError, errmsg
|
94
92
|
when 20118, 20119
|
data/lib/mixin_bot/nfo.rb
CHANGED