mixin_bot 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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 +77 -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/legacy_transfer.rb +2 -2
- data/lib/mixin_bot/api/multisig.rb +5 -5
- data/lib/mixin_bot/api/output.rb +3 -1
- data/lib/mixin_bot/api/payment.rb +1 -1
- data/lib/mixin_bot/api/pin.rb +2 -2
- data/lib/mixin_bot/api/rpc.rb +6 -6
- data/lib/mixin_bot/api/transaction.rb +134 -23
- data/lib/mixin_bot/api/transfer.rb +28 -12
- data/lib/mixin_bot/api/user.rb +12 -7
- 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 +23 -10
- 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: 2162191a7068afafb913aa9b898c7915c133b732c25b785c1ec8cbf672fd3edd
|
4
|
+
data.tar.gz: 31303c5e3c9dfb3759c943c63d74a67f7efb7a9cbdfbee9a823ba6f7dc7c6ab2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9cef773f5bde48f7d1697b5be99251ec6b4d2dcd5dd15c1d9b7aa60d90089a3476f8b696b5f9e7c93acbbf464d35583519b51d7fdff70cd842c9a97d7a4b2100
|
7
|
+
data.tar.gz: 2a16d609f841f1f2c0b85b217254e5368e042e0e2dc78817f7bee89774e90b12f674c8b3c67c4c73644ef02ff8f2a616109cd0659f63e8bed128cae50431152d
|
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,77 @@
|
|
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 collection_collectibles(hash, offset: 0)
|
19
|
+
path = "/safe/inscriptions/collections/#{hash}/items"
|
20
|
+
|
21
|
+
client.get path, offset:
|
22
|
+
end
|
23
|
+
|
24
|
+
def collectibles(members: [], access_token: nil)
|
25
|
+
unspent_outputs = safe_outputs(state: :unspent, members:, access_token:)['data']
|
26
|
+
unspent_outputs.select { |output| output['inscription_hash'].present? }
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_collectible_transfer(utxo, **kwargs)
|
30
|
+
# verify collectible
|
31
|
+
utxo = utxo.with_indifferent_access
|
32
|
+
raise MixinBot::ArgumentError, 'not a valid collectible' unless utxo['inscription_hash'].present?
|
33
|
+
|
34
|
+
# verify members
|
35
|
+
members = [kwargs[:members]].flatten.compact
|
36
|
+
raise ArgumentError, 'members required' if members.blank?
|
37
|
+
|
38
|
+
threshold = kwargs[:threshold] || members.length
|
39
|
+
request_id = kwargs[:request_id] || kwargs[:trace_id] || SecureRandom.uuid
|
40
|
+
|
41
|
+
memo = kwargs[:memo] || ''
|
42
|
+
|
43
|
+
# build transaction
|
44
|
+
tx = build_safe_transaction(
|
45
|
+
utxos: [utxo],
|
46
|
+
receivers: [{
|
47
|
+
members:,
|
48
|
+
threshold:,
|
49
|
+
amount: utxo['amount']
|
50
|
+
}],
|
51
|
+
extra: memo
|
52
|
+
)
|
53
|
+
|
54
|
+
# encode transaction
|
55
|
+
raw = MixinBot.utils.encode_raw_transaction tx
|
56
|
+
|
57
|
+
# verify transaction
|
58
|
+
request = create_safe_transaction_request(request_id, raw)['data']
|
59
|
+
|
60
|
+
# sign transaction
|
61
|
+
spend_key = MixinBot.utils.decode_key(kwargs[:spend_key]) || config.spend_key
|
62
|
+
signed_raw = MixinBot.api.sign_safe_transaction(
|
63
|
+
raw:,
|
64
|
+
utxos: [utxo],
|
65
|
+
request: request[0],
|
66
|
+
spend_key:
|
67
|
+
)
|
68
|
+
|
69
|
+
# submit transaction
|
70
|
+
send_safe_transaction(
|
71
|
+
request_id,
|
72
|
+
signed_raw
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
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'
|
@@ -4,7 +4,7 @@ module MixinBot
|
|
4
4
|
class API
|
5
5
|
module LegacyTransfer
|
6
6
|
TRANSFER_ARGUMENTS = %i[asset_id opponent_id amount].freeze
|
7
|
-
def
|
7
|
+
def create_legacy_transfer(pin, **kwargs)
|
8
8
|
raise ArgumentError, "#{TRANSFER_ARGUMENTS.join(', ')} are needed for create transfer" unless TRANSFER_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
9
9
|
|
10
10
|
asset_id = kwargs[:asset_id]
|
@@ -33,7 +33,7 @@ module MixinBot
|
|
33
33
|
client.post path, **payload
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def legacy_transfer(trace_id, access_token: nil)
|
37
37
|
path = format('/transfers/trace/%<trace_id>s', trace_id:)
|
38
38
|
client.get path, access_token:
|
39
39
|
end
|
@@ -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
|
@@ -11,7 +11,7 @@ module MixinBot
|
|
11
11
|
memo = kwargs[:memo] || ''
|
12
12
|
trace_id = kwargs[:trace_id] || SecureRandom.uuid
|
13
13
|
|
14
|
-
mix_address = MixinBot.utils.build_mix_address(members
|
14
|
+
mix_address = MixinBot.utils.build_mix_address(members:, threshold:)
|
15
15
|
|
16
16
|
"https://mixin.one/pay/#{mix_address}?amount=#{amount}&asset=#{asset_id}&memo=#{memo}&trace=#{trace_id}"
|
17
17
|
end
|
data/lib/mixin_bot/api/pin.rb
CHANGED
@@ -30,7 +30,7 @@ module MixinBot
|
|
30
30
|
|
31
31
|
# https://developers.mixin.one/api/alpha-mixin-network/create-pin/
|
32
32
|
def update_pin(pin:, old_pin: nil)
|
33
|
-
old_pin ||= MixinBot.config.pin
|
33
|
+
# old_pin ||= MixinBot.config.pin
|
34
34
|
raise ArgumentError, 'invalid old pin' if old_pin.present? && old_pin.length != 6
|
35
35
|
|
36
36
|
path = '/pin/update'
|
@@ -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
@@ -4,7 +4,7 @@ module MixinBot
|
|
4
4
|
class API
|
5
5
|
module Rpc
|
6
6
|
def rpc_proxy(method, params = [], access_token: nil)
|
7
|
-
path = '/external/
|
7
|
+
path = '/external/kernel'
|
8
8
|
payload = {
|
9
9
|
method:,
|
10
10
|
params:
|
@@ -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(members: receivers, threshold: 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
|
@@ -15,8 +15,17 @@ module MixinBot
|
|
15
15
|
# spend_key: string / nil,
|
16
16
|
# }
|
17
17
|
def create_safe_transfer(**kwargs)
|
18
|
-
|
19
|
-
raise ArgumentError, '
|
18
|
+
utxos = kwargs[:utxos]
|
19
|
+
raise ArgumentError, 'utxos must be array' if utxos.present? && !utxos.is_a?(Array)
|
20
|
+
|
21
|
+
asset_id =
|
22
|
+
if utxos.present?
|
23
|
+
utxos.first['asset_id']
|
24
|
+
else
|
25
|
+
kwargs[:asset_id]
|
26
|
+
end
|
27
|
+
|
28
|
+
raise ArgumentError, 'utxos or asset_id required' if utxos.blank? && asset_id.blank?
|
20
29
|
|
21
30
|
amount = kwargs[:amount]&.to_d
|
22
31
|
raise ArgumentError, 'amount required' if amount.blank?
|
@@ -29,15 +38,7 @@ module MixinBot
|
|
29
38
|
memo = kwargs[:memo] || ''
|
30
39
|
|
31
40
|
# 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
|
41
|
+
utxos ||= build_utxos(asset_id:, amount:)
|
41
42
|
|
42
43
|
# step 2: build transaction
|
43
44
|
tx = build_safe_transaction(
|
@@ -56,7 +57,7 @@ module MixinBot
|
|
56
57
|
|
57
58
|
# step 4: sign transaction
|
58
59
|
spend_key = MixinBot.utils.decode_key(kwargs[:spend_key]) || config.spend_key
|
59
|
-
signed_raw = sign_safe_transaction(
|
60
|
+
signed_raw = MixinBot.api.sign_safe_transaction(
|
60
61
|
raw:,
|
61
62
|
utxos:,
|
62
63
|
request: request[0],
|
@@ -69,6 +70,21 @@ module MixinBot
|
|
69
70
|
signed_raw
|
70
71
|
)
|
71
72
|
end
|
73
|
+
alias create_transfer create_safe_transfer
|
74
|
+
|
75
|
+
def build_utxos(asset_id:, amount:)
|
76
|
+
outputs = safe_outputs(state: 'unspent', asset: asset_id, limit: 500)['data'].sort_by { |o| o['amount'].to_d }
|
77
|
+
|
78
|
+
utxos = []
|
79
|
+
outputs.each do |output|
|
80
|
+
break if utxos.sum { |o| o['amount'].to_d } >= amount
|
81
|
+
|
82
|
+
utxos.shift if utxos.size >= 256
|
83
|
+
utxos << output
|
84
|
+
end
|
85
|
+
|
86
|
+
utxos
|
87
|
+
end
|
72
88
|
end
|
73
89
|
end
|
74
90
|
end
|
data/lib/mixin_bot/api/user.rb
CHANGED
@@ -49,9 +49,9 @@ module MixinBot
|
|
49
49
|
keystore = {
|
50
50
|
app_id: user['data']['user_id'],
|
51
51
|
session_id: user['data']['session_id'],
|
52
|
-
private_key
|
53
|
-
|
54
|
-
spend_key:
|
52
|
+
session_private_key: private_key,
|
53
|
+
server_public_key: user['data']['pin_token_base64'],
|
54
|
+
spend_key:
|
55
55
|
}
|
56
56
|
user_api = MixinBot::API.new(**keystore)
|
57
57
|
|
@@ -60,7 +60,7 @@ module MixinBot
|
|
60
60
|
# wait for tip pin update in server
|
61
61
|
sleep 1
|
62
62
|
|
63
|
-
user_api.safe_register spend_key
|
63
|
+
user_api.safe_register keystore[:spend_key]
|
64
64
|
|
65
65
|
keystore
|
66
66
|
end
|
@@ -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:
|