mixin_bot 0.12.0 → 1.0.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/address.rb +21 -0
- data/lib/mixin_bot/api/app.rb +5 -11
- data/lib/mixin_bot/api/asset.rb +9 -16
- data/lib/mixin_bot/api/attachment.rb +27 -22
- data/lib/mixin_bot/api/auth.rb +31 -53
- data/lib/mixin_bot/api/blaze.rb +4 -3
- data/lib/mixin_bot/api/collectible.rb +60 -58
- data/lib/mixin_bot/api/conversation.rb +29 -49
- data/lib/mixin_bot/api/encrypted_message.rb +17 -17
- data/lib/mixin_bot/api/legacy_multisig.rb +87 -0
- data/lib/mixin_bot/api/legacy_output.rb +50 -0
- data/lib/mixin_bot/api/legacy_payment.rb +31 -0
- data/lib/mixin_bot/api/legacy_snapshot.rb +39 -0
- data/lib/mixin_bot/api/legacy_transaction.rb +173 -0
- data/lib/mixin_bot/api/legacy_transfer.rb +42 -0
- data/lib/mixin_bot/api/me.rb +13 -17
- data/lib/mixin_bot/api/message.rb +13 -10
- data/lib/mixin_bot/api/multisig.rb +16 -221
- data/lib/mixin_bot/api/output.rb +46 -0
- data/lib/mixin_bot/api/payment.rb +9 -20
- data/lib/mixin_bot/api/pin.rb +57 -65
- data/lib/mixin_bot/api/rpc.rb +9 -11
- data/lib/mixin_bot/api/snapshot.rb +15 -29
- data/lib/mixin_bot/api/tip.rb +43 -0
- data/lib/mixin_bot/api/transaction.rb +184 -60
- data/lib/mixin_bot/api/transfer.rb +64 -32
- data/lib/mixin_bot/api/user.rb +83 -53
- data/lib/mixin_bot/api/withdraw.rb +52 -53
- data/lib/mixin_bot/api.rb +78 -45
- data/lib/mixin_bot/cli/api.rb +151 -8
- data/lib/mixin_bot/cli/utils.rb +14 -4
- data/lib/mixin_bot/cli.rb +13 -10
- data/lib/mixin_bot/client.rb +76 -127
- data/lib/mixin_bot/configuration.rb +98 -0
- data/lib/mixin_bot/nfo.rb +174 -0
- data/lib/mixin_bot/transaction.rb +505 -0
- data/lib/mixin_bot/utils/address.rb +108 -0
- data/lib/mixin_bot/utils/crypto.rb +182 -0
- data/lib/mixin_bot/utils/decoder.rb +58 -0
- data/lib/mixin_bot/utils/encoder.rb +63 -0
- data/lib/mixin_bot/utils.rb +8 -109
- data/lib/mixin_bot/uuid.rb +41 -0
- data/lib/mixin_bot/version.rb +1 -1
- data/lib/mixin_bot.rb +39 -14
- data/lib/mvm/bridge.rb +2 -19
- data/lib/mvm/client.rb +11 -33
- data/lib/mvm/nft.rb +4 -4
- data/lib/mvm/registry.rb +9 -9
- data/lib/mvm/scan.rb +3 -5
- data/lib/mvm.rb +5 -6
- metadata +101 -44
- data/lib/mixin_bot/utils/nfo.rb +0 -176
- data/lib/mixin_bot/utils/transaction.rb +0 -478
- data/lib/mixin_bot/utils/uuid.rb +0 -43
@@ -3,77 +3,67 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Conversation
|
6
|
-
def conversation(conversation_id)
|
7
|
-
path = format('/conversations/%<conversation_id>s', conversation_id:
|
8
|
-
|
9
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
10
|
-
client.get(path, headers: { 'Authorization': authorization })
|
6
|
+
def conversation(conversation_id, access_token: nil)
|
7
|
+
path = format('/conversations/%<conversation_id>s', conversation_id:)
|
8
|
+
client.get path, access_token:
|
11
9
|
end
|
12
|
-
alias read_conversation conversation
|
13
10
|
|
14
11
|
def conversation_by_user_id(user_id)
|
15
|
-
conversation_id =
|
16
|
-
|
12
|
+
conversation_id = unique_uuid user_id
|
13
|
+
conversation conversation_id
|
17
14
|
end
|
18
|
-
alias read_conversation_by_user_id conversation_by_user_id
|
19
15
|
|
20
|
-
def create_conversation(
|
16
|
+
def create_conversation(**kwargs)
|
21
17
|
path = '/conversations'
|
22
18
|
payload = {
|
23
|
-
category: category,
|
24
|
-
conversation_id: conversation_id || SecureRandom.uuid,
|
25
|
-
name: name,
|
26
|
-
participants: participants
|
27
|
-
}
|
19
|
+
category: kwargs[:category],
|
20
|
+
conversation_id: kwargs[:conversation_id] || SecureRandom.uuid,
|
21
|
+
name: kwargs[:name],
|
22
|
+
participants: kwargs[:participants]
|
23
|
+
}.compact_blank
|
28
24
|
|
29
|
-
|
30
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
31
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
25
|
+
client.post path, **payload, access_token: kwargs[:access_token]
|
32
26
|
end
|
33
27
|
|
34
28
|
def create_group_conversation(user_ids:, name:, conversation_id: nil, access_token: nil)
|
35
29
|
create_conversation(
|
36
30
|
category: 'GROUP',
|
37
|
-
conversation_id
|
38
|
-
name
|
31
|
+
conversation_id:,
|
32
|
+
name:,
|
39
33
|
participants: user_ids.map(&->(participant) { { user_id: participant } }),
|
40
|
-
access_token:
|
34
|
+
access_token:
|
41
35
|
)
|
42
36
|
end
|
43
37
|
|
44
38
|
def create_contact_conversation(user_id, access_token: nil)
|
45
39
|
create_conversation(
|
46
40
|
category: 'CONTACT',
|
47
|
-
conversation_id:
|
41
|
+
conversation_id: unique_uuid(user_id),
|
48
42
|
participants: [
|
49
43
|
{
|
50
|
-
user_id:
|
44
|
+
user_id:
|
51
45
|
}
|
52
46
|
],
|
53
|
-
access_token:
|
47
|
+
access_token:
|
54
48
|
)
|
55
49
|
end
|
56
50
|
|
57
51
|
def update_group_conversation_name(name:, conversation_id:, access_token: nil)
|
58
52
|
path = format('/conversations/%<id>s', id: conversation_id)
|
59
53
|
payload = {
|
60
|
-
name:
|
54
|
+
name:
|
61
55
|
}
|
62
56
|
|
63
|
-
|
64
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
65
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
57
|
+
client.post path, **payload, access_token:
|
66
58
|
end
|
67
59
|
|
68
60
|
def update_group_conversation_announcement(announcement:, conversation_id:, access_token: nil)
|
69
61
|
path = format('/conversations/%<id>s', id: conversation_id)
|
70
62
|
payload = {
|
71
|
-
announcement:
|
63
|
+
announcement:
|
72
64
|
}
|
73
65
|
|
74
|
-
|
75
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
76
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
66
|
+
client.post path, **payload, access_token:
|
77
67
|
end
|
78
68
|
|
79
69
|
# participants = [{ user_id: "" }]
|
@@ -81,9 +71,7 @@ module MixinBot
|
|
81
71
|
path = format('/conversations/%<id>s/participants/ADD', id: conversation_id)
|
82
72
|
payload = user_ids.map(&->(participant) { { user_id: participant } })
|
83
73
|
|
84
|
-
|
85
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
86
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
74
|
+
client.post path, *payload, access_token:
|
87
75
|
end
|
88
76
|
|
89
77
|
# participants = [{ user_id: "" }]
|
@@ -91,25 +79,19 @@ module MixinBot
|
|
91
79
|
path = format('/conversations/%<id>s/participants/REMOVE', id: conversation_id)
|
92
80
|
payload = user_ids.map(&->(participant) { { user_id: participant } })
|
93
81
|
|
94
|
-
|
95
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
96
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
82
|
+
client.post path, *payload, access_token:
|
97
83
|
end
|
98
84
|
|
99
85
|
def exit_conversation(conversation_id, access_token: nil)
|
100
86
|
path = format('/conversations/%<id>s/exit', id: conversation_id)
|
101
87
|
|
102
|
-
|
103
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
104
|
-
client.post(path, headers: { 'Authorization': authorization })
|
88
|
+
client.post path, access_token:
|
105
89
|
end
|
106
90
|
|
107
91
|
def rotate_conversation(conversation_id, access_token: nil)
|
108
92
|
path = format('/conversations/%<id>s/rotate', id: conversation_id)
|
109
93
|
|
110
|
-
|
111
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
112
|
-
client.post(path, headers: { 'Authorization': authorization })
|
94
|
+
client.post path, access_token:
|
113
95
|
end
|
114
96
|
|
115
97
|
# participants = [{ user_id: "", role: "ADMIN" }]
|
@@ -117,14 +99,12 @@ module MixinBot
|
|
117
99
|
path = format('/conversations/%<id>s/participants/ROLE', id: conversation_id)
|
118
100
|
payload = participants
|
119
101
|
|
120
|
-
|
121
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
122
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
102
|
+
client.post path, *payload, access_token:
|
123
103
|
end
|
124
104
|
|
125
105
|
def unique_uuid(user_id, opponent_id = nil)
|
126
|
-
opponent_id ||=
|
127
|
-
MixinBot
|
106
|
+
opponent_id ||= config.app_id
|
107
|
+
MixinBot.utils.unique_uuid user_id, opponent_id
|
128
108
|
end
|
129
109
|
alias unique_conversation_id unique_uuid
|
130
110
|
end
|
@@ -90,11 +90,11 @@ module MixinBot
|
|
90
90
|
category: options[:category],
|
91
91
|
quote_message_id: options[:quote_message_id],
|
92
92
|
message_id: options[:message_id] || SecureRandom.uuid,
|
93
|
-
data_base64
|
94
|
-
checksum
|
93
|
+
data_base64:,
|
94
|
+
checksum:,
|
95
95
|
recipient_sessions: session_ids.map(&->(s) { { session_id: s } }),
|
96
96
|
silent: false
|
97
|
-
}
|
97
|
+
}.compact
|
98
98
|
end
|
99
99
|
|
100
100
|
def send_encrypted_messages(messages)
|
@@ -104,19 +104,19 @@ module MixinBot
|
|
104
104
|
# http post request
|
105
105
|
def send_encrypted_message(payload)
|
106
106
|
path = '/encrypted_messages'
|
107
|
-
payload = [payload]
|
108
|
-
|
109
|
-
|
110
|
-
client.post
|
107
|
+
payload = [payload] if payload.is_a? Hash
|
108
|
+
raise ArgumentError, 'Wrong payload format!' unless payload.is_a? Array
|
109
|
+
|
110
|
+
client.post path, *payload
|
111
111
|
end
|
112
112
|
|
113
113
|
def encrypt_message(data, sessions = [], sk: nil, pk: nil)
|
114
114
|
raise ArgumentError, 'Wrong sessions format!' unless sessions.all?(&->(s) { s.key?('session_id') && s.key?('public_key') })
|
115
115
|
|
116
|
-
sk
|
117
|
-
pk ||=
|
116
|
+
sk ||= config.session_private_key[0...32]
|
117
|
+
pk ||= config.session_private_key[32...]
|
118
118
|
|
119
|
-
|
119
|
+
Digest::MD5.hexdigest sessions.map(&->(s) { s['session_id'] }).sort.join
|
120
120
|
encrypter = OpenSSL::Cipher.new('AES-128-GCM').encrypt
|
121
121
|
key = encrypter.random_key
|
122
122
|
nounce = encrypter.random_iv
|
@@ -128,14 +128,14 @@ module MixinBot
|
|
128
128
|
bytes = [1]
|
129
129
|
bytes += [sessions.size].pack('v*').bytes
|
130
130
|
bytes += JOSE::JWA::Ed25519.pk_to_curve25519(pk).bytes
|
131
|
-
|
131
|
+
|
132
132
|
sessions.each do |session|
|
133
133
|
aes_key = JOSE::JWA::X25519.shared_secret(
|
134
134
|
Base64.urlsafe_decode64(session['public_key']),
|
135
135
|
JOSE::JWA::Ed25519.secret_to_curve25519(sk)
|
136
136
|
)
|
137
137
|
|
138
|
-
padding = 16 - key.size % 16
|
138
|
+
padding = 16 - (key.size % 16)
|
139
139
|
padtext = ([padding] * padding).pack('C*')
|
140
140
|
|
141
141
|
encrypter = OpenSSL::Cipher.new('AES-256-CBC').encrypt
|
@@ -143,7 +143,7 @@ module MixinBot
|
|
143
143
|
iv = encrypter.random_iv
|
144
144
|
encrypter.iv = iv
|
145
145
|
|
146
|
-
bytes += (MixinBot::
|
146
|
+
bytes += (MixinBot::UUID.new(hex: session['session_id']).packed + iv).bytes
|
147
147
|
bytes += encrypter.update(key + padtext).bytes
|
148
148
|
end
|
149
149
|
|
@@ -156,19 +156,19 @@ module MixinBot
|
|
156
156
|
def decrypt_message(data, sk: nil, si: nil)
|
157
157
|
bytes = Base64.urlsafe_decode64(data).bytes
|
158
158
|
|
159
|
-
si ||= session_id
|
160
|
-
sk ||=
|
159
|
+
si ||= config.session_id
|
160
|
+
sk ||= config.session_private_key[0...32]
|
161
161
|
|
162
162
|
size = 16 + 48
|
163
163
|
return '' if bytes.size < 1 + 2 + 32 + size + 12
|
164
164
|
|
165
165
|
session_length = bytes[1...3].pack('v*').unpack1('C*')
|
166
|
-
prefix_size = 35 + session_length * size
|
166
|
+
prefix_size = 35 + (session_length * size)
|
167
167
|
|
168
168
|
i = 35
|
169
169
|
key = ''
|
170
170
|
while i < prefix_size
|
171
|
-
uuid = MixinBot::
|
171
|
+
uuid = MixinBot::UUID.new(raw: bytes[i...(i + 16)].pack('C*')).unpacked
|
172
172
|
if uuid == si
|
173
173
|
pub = bytes[3...35]
|
174
174
|
aes_key = JOSE::JWA::X25519.shared_secret(
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module LegacyMultisig
|
6
|
+
MULTISIG_REQUEST_ACTIONS = %i[sign unlock].freeze
|
7
|
+
def create_multisig_request(action, raw, access_token: nil)
|
8
|
+
raise ArgumentError, "request action is limited in #{MULTISIG_REQUEST_ACTIONS.join(', ')}" unless MULTISIG_REQUEST_ACTIONS.include? action.to_sym
|
9
|
+
|
10
|
+
path = '/multisigs/requests'
|
11
|
+
payload = {
|
12
|
+
action:,
|
13
|
+
raw:
|
14
|
+
}
|
15
|
+
client.post path, **payload, access_token:
|
16
|
+
end
|
17
|
+
|
18
|
+
# transfer from the multisig address
|
19
|
+
def create_sign_multisig_request(raw, access_token: nil)
|
20
|
+
create_multisig_request 'sign', raw, access_token:
|
21
|
+
end
|
22
|
+
|
23
|
+
# transfer from the multisig address
|
24
|
+
# create a request for unlock a multi-sign
|
25
|
+
def create_unlock_multisig_request(raw, access_token: nil)
|
26
|
+
create_multisig_request 'unlock', raw, access_token:
|
27
|
+
end
|
28
|
+
|
29
|
+
def sign_multisig_request(request_id, pin = nil)
|
30
|
+
pin ||= config.pin
|
31
|
+
path = format('/multisigs/requests/%<request_id>s/sign', request_id:)
|
32
|
+
payload =
|
33
|
+
if pin.length > 6
|
34
|
+
{
|
35
|
+
pin_base64: encrypt_tip_pin(pin, 'TIP:MULTISIG:REQUEST:SIGN:', request_id)
|
36
|
+
}
|
37
|
+
else
|
38
|
+
{
|
39
|
+
pin: encrypt_pin(pin)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
client.post path, **payload, access_token:
|
44
|
+
end
|
45
|
+
|
46
|
+
def unlock_multisig_request(request_id, pin = nil)
|
47
|
+
pin ||= config.pin
|
48
|
+
|
49
|
+
path = format('/multisigs/requests/%<request_id>s/unlock', request_id:)
|
50
|
+
payload =
|
51
|
+
if pin.length > 6
|
52
|
+
{
|
53
|
+
pin_base64: encrypt_tip_pin(pin, 'TIP:MULTISIG:REQUEST:UNLOCK:', request_id)
|
54
|
+
}
|
55
|
+
else
|
56
|
+
{
|
57
|
+
pin: encrypt_pin(pin)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
client.post path, **payload
|
62
|
+
end
|
63
|
+
|
64
|
+
# pay to the multisig address
|
65
|
+
# used for create multisig payment code_id
|
66
|
+
def create_multisig_payment(**kwargs)
|
67
|
+
path = '/payments'
|
68
|
+
payload = {
|
69
|
+
asset_id: kwargs[:asset_id],
|
70
|
+
amount: format('%.8f', kwargs[:amount].to_d),
|
71
|
+
trace_id: kwargs[:trace_id] || SecureRandom.uuid,
|
72
|
+
memo: kwargs[:memo],
|
73
|
+
opponent_multisig: {
|
74
|
+
receivers: kwargs[:receivers],
|
75
|
+
threshold: kwargs[:threshold]
|
76
|
+
}
|
77
|
+
}
|
78
|
+
client.post path, **payload, access_token: kwargs[:access_token]
|
79
|
+
end
|
80
|
+
|
81
|
+
def verify_multisig(code_id, access_token: nil)
|
82
|
+
path = format('/codes/%<code_id>s', code_id:)
|
83
|
+
client.get path, access_token:
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module LegacyOutput
|
6
|
+
def outputs(**kwargs)
|
7
|
+
limit = kwargs[:limit] || 100
|
8
|
+
offset = kwargs[:offset] || ''
|
9
|
+
state = kwargs[:state] || ''
|
10
|
+
members = kwargs[:members] || []
|
11
|
+
threshold = kwargs[:threshold] || ''
|
12
|
+
access_token = kwargs[:access_token]
|
13
|
+
members = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
|
14
|
+
|
15
|
+
path = '/multisigs/outputs'
|
16
|
+
params = {
|
17
|
+
limit:,
|
18
|
+
offset:,
|
19
|
+
state:,
|
20
|
+
members:,
|
21
|
+
threshold:
|
22
|
+
}.compact_blank
|
23
|
+
|
24
|
+
client.get path, **params, access_token:
|
25
|
+
end
|
26
|
+
alias multisigs outputs
|
27
|
+
alias multisig_outputs outputs
|
28
|
+
|
29
|
+
def create_output(receivers:, index:, hint: nil, access_token: nil)
|
30
|
+
path = '/outputs'
|
31
|
+
payload = {
|
32
|
+
receivers:,
|
33
|
+
index:,
|
34
|
+
hint:
|
35
|
+
}
|
36
|
+
client.post path, **payload, access_token:
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_output(receivers:, index:, amount:, threshold:, hint: nil)
|
40
|
+
_output = create_output(receivers:, index:, hint:)
|
41
|
+
{
|
42
|
+
amount: format('%.8f', amount.to_d.to_r),
|
43
|
+
script: build_threshold_script(threshold),
|
44
|
+
mask: _output['mask'],
|
45
|
+
keys: _output['keys']
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module LegacyPayment
|
6
|
+
def pay_url(**kwargs)
|
7
|
+
format(
|
8
|
+
'https://mixin.one/pay?recipient=%<recipient_id>s&asset=%<asset>s&amount=%<amount>s&trace=%<trace>s&memo=%<memo>s',
|
9
|
+
recipient_id: kwargs[:recipient_id],
|
10
|
+
asset: kwargs[:asset_id],
|
11
|
+
amount: kwargs[:amount].to_s,
|
12
|
+
trace: kwargs[:trace],
|
13
|
+
memo: kwargs[:memo]
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
# https://developers.mixin.one/api/alpha-mixin-network/verify-payment/
|
18
|
+
def verify_payment(**kwargs)
|
19
|
+
path = '/payments'
|
20
|
+
payload = {
|
21
|
+
asset_id: kwargs[:asset_id],
|
22
|
+
opponent_id: kwargs[:opponent_id],
|
23
|
+
amount: kwargs[:amount].to_s,
|
24
|
+
trace_id: kwargs[:trace]
|
25
|
+
}
|
26
|
+
|
27
|
+
client.post path, **payload
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module LegacySnapshot
|
6
|
+
def network_snapshots(**kwargs)
|
7
|
+
path = '/network/snapshots'
|
8
|
+
params = {
|
9
|
+
limit: kwargs[:limit],
|
10
|
+
offset: kwargs[:offset],
|
11
|
+
asset: kwargs[:asset],
|
12
|
+
order: kwargs[:order]
|
13
|
+
}
|
14
|
+
|
15
|
+
client.get path, **params, access_token: kwargs[:access_token]
|
16
|
+
end
|
17
|
+
|
18
|
+
def snapshots(**kwargs)
|
19
|
+
path = '/snapshots'
|
20
|
+
|
21
|
+
params = {
|
22
|
+
limit: kwargs[:limit],
|
23
|
+
offset: kwargs[:offset],
|
24
|
+
asset: kwargs[:asset],
|
25
|
+
opponent: kwargs[:opponent],
|
26
|
+
order: kwargs[:order]
|
27
|
+
}
|
28
|
+
|
29
|
+
client.get path, **params, access_token: kwargs[:access_token]
|
30
|
+
end
|
31
|
+
|
32
|
+
def network_snapshot(snapshot_id, **kwargs)
|
33
|
+
path = format('/network/snapshots/%<snapshot_id>s', snapshot_id:)
|
34
|
+
|
35
|
+
client.get path, access_token: kwargs[:access_token]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module LegacyTransaction
|
6
|
+
LEGACY_TX_VERSION = 0x04
|
7
|
+
|
8
|
+
# use safe transaction protocol instead
|
9
|
+
# kwargs:
|
10
|
+
# {
|
11
|
+
# senders: [ uuid ],
|
12
|
+
# senders_threshold: integer,
|
13
|
+
# receivers: [ uuid ],
|
14
|
+
# receivers_threshold: integer,
|
15
|
+
# asset_id: uuid,
|
16
|
+
# amount: string / float,
|
17
|
+
# memo: string,
|
18
|
+
# }
|
19
|
+
RAW_TRANSACTION_ARGUMENTS = %i[utxos senders senders_threshold receivers receivers_threshold amount].freeze
|
20
|
+
def build_raw_transaction(**kwargs)
|
21
|
+
raise ArgumentError, "#{RAW_TRANSACTION_ARGUMENTS.join(', ')} are needed for build raw transaction" unless RAW_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
22
|
+
|
23
|
+
senders = kwargs[:senders]
|
24
|
+
senders_threshold = kwargs[:senders_threshold]
|
25
|
+
receivers = kwargs[:receivers]
|
26
|
+
receivers_threshold = kwargs[:receivers_threshold]
|
27
|
+
amount = kwargs[:amount]
|
28
|
+
asset_id = kwargs[:asset_id]
|
29
|
+
asset_mixin_id = kwargs[:asset_mixin_id]
|
30
|
+
utxos = kwargs[:utxos]
|
31
|
+
extra = kwargs[:extra]
|
32
|
+
access_token = kwargs[:access_token]
|
33
|
+
outputs = kwargs[:outputs] || []
|
34
|
+
hint = kwargs[:hint]
|
35
|
+
version = kwargs[:version] || LEGACY_TX_VERSION
|
36
|
+
|
37
|
+
raise 'access_token required!' if access_token.nil? && !senders.include?(config.app_id)
|
38
|
+
|
39
|
+
amount = amount.to_d.round(8)
|
40
|
+
input_amount = utxos.map(
|
41
|
+
&lambda { |utxo|
|
42
|
+
utxo['amount'].to_d
|
43
|
+
}
|
44
|
+
).sum
|
45
|
+
|
46
|
+
if input_amount < amount
|
47
|
+
raise format(
|
48
|
+
'not enough amount! %<input_amount>s < %<amount>s',
|
49
|
+
input_amount:,
|
50
|
+
amount:
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
inputs = utxos.map(
|
55
|
+
&lambda { |utx|
|
56
|
+
{
|
57
|
+
'hash' => utx['transaction_hash'],
|
58
|
+
'index' => utx['output_index']
|
59
|
+
}
|
60
|
+
}
|
61
|
+
)
|
62
|
+
|
63
|
+
if outputs.empty?
|
64
|
+
receivers_threshold = 1 if receivers.size == 1
|
65
|
+
output0 = build_output(
|
66
|
+
receivers:,
|
67
|
+
index: 0,
|
68
|
+
amount:,
|
69
|
+
threshold: receivers_threshold,
|
70
|
+
hint:
|
71
|
+
)
|
72
|
+
outputs.push output0
|
73
|
+
|
74
|
+
if input_amount > amount
|
75
|
+
output1 = build_output(
|
76
|
+
receivers: senders,
|
77
|
+
index: 1,
|
78
|
+
amount: input_amount - amount,
|
79
|
+
threshold: senders_threshold,
|
80
|
+
hint:
|
81
|
+
)
|
82
|
+
outputs.push output1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
asset = asset_mixin_id || SHA3::Digest::SHA256.hexdigest(asset_id)
|
87
|
+
{
|
88
|
+
version:,
|
89
|
+
asset:,
|
90
|
+
inputs:,
|
91
|
+
outputs:,
|
92
|
+
extra:
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
# use safe transaction protocol instead
|
97
|
+
MULTISIG_TRANSACTION_ARGUMENTS = %i[asset_id receivers threshold amount].freeze
|
98
|
+
def create_multisig_transaction(pin, **options)
|
99
|
+
raise ArgumentError, "#{MULTISIG_TRANSACTION_ARGUMENTS.join(', ')} are needed for create multisig transaction" unless MULTISIG_TRANSACTION_ARGUMENTS.all? { |param| options.keys.include? param }
|
100
|
+
|
101
|
+
asset_id = options[:asset_id]
|
102
|
+
receivers = options[:receivers].sort
|
103
|
+
threshold = options[:threshold]
|
104
|
+
amount = format('%.8f', options[:amount].to_d.to_r)
|
105
|
+
memo = options[:memo]
|
106
|
+
trace_id = options[:trace_id] || SecureRandom.uuid
|
107
|
+
|
108
|
+
path = '/transactions'
|
109
|
+
payload = {
|
110
|
+
asset_id:,
|
111
|
+
opponent_multisig: {
|
112
|
+
receivers:,
|
113
|
+
threshold:
|
114
|
+
},
|
115
|
+
amount:,
|
116
|
+
trace_id:,
|
117
|
+
memo:
|
118
|
+
}
|
119
|
+
|
120
|
+
if pin.length > 6
|
121
|
+
payload[:pin_base64] = encrypt_tip_pin(pin, 'TIP:TRANSACTION:CREATE:', asset_id, receivers.join, threshold, amount, trace_id, memo)
|
122
|
+
else
|
123
|
+
payload[:pin] = encrypt_pin(pin)
|
124
|
+
end
|
125
|
+
|
126
|
+
client.post path, **payload
|
127
|
+
end
|
128
|
+
|
129
|
+
# use safe transaction protocol instead
|
130
|
+
MAINNET_TRANSACTION_ARGUMENTS = %i[asset_id opponent_id amount].freeze
|
131
|
+
def create_mainnet_transaction(pin, **options)
|
132
|
+
raise ArgumentError, "#{MAINNET_TRANSACTION_ARGUMENTS.join(', ')} are needed for create main net transactions" unless MAINNET_TRANSACTION_ARGUMENTS.all? { |param| options.keys.include? param }
|
133
|
+
|
134
|
+
asset_id = options[:asset_id]
|
135
|
+
opponent_id = options[:opponent_id]
|
136
|
+
amount = format('%.8f', options[:amount].to_d)
|
137
|
+
memo = options[:memo]
|
138
|
+
trace_id = options[:trace_id] || SecureRandom.uuid
|
139
|
+
|
140
|
+
path = '/transactions'
|
141
|
+
payload = {
|
142
|
+
asset_id:,
|
143
|
+
opponent_id:,
|
144
|
+
amount:,
|
145
|
+
trace_id:,
|
146
|
+
memo:
|
147
|
+
}
|
148
|
+
|
149
|
+
if pin.length > 6
|
150
|
+
payload[:pin_base64] = encrypt_tip_pin(pin, 'TIP:TRANSACTION:CREATE:', asset_id, opponent_id, amount, trace_id, memo)
|
151
|
+
else
|
152
|
+
payload[:pin] = encrypt_pin(pin)
|
153
|
+
end
|
154
|
+
|
155
|
+
client.post path, **payload
|
156
|
+
end
|
157
|
+
|
158
|
+
# use safe transaction protocol instead
|
159
|
+
def transactions(**options)
|
160
|
+
path = '/external/transactions'
|
161
|
+
params = {
|
162
|
+
limit: options[:limit],
|
163
|
+
offset: options[:offset],
|
164
|
+
asset: options[:asset],
|
165
|
+
destination: options[:destination],
|
166
|
+
tag: options[:tag]
|
167
|
+
}
|
168
|
+
|
169
|
+
client.get path, **params
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module LegacyTransfer
|
6
|
+
TRANSFER_ARGUMENTS = %i[asset_id opponent_id amount].freeze
|
7
|
+
def create_transfer(pin, **kwargs)
|
8
|
+
raise ArgumentError, "#{TRANSFER_ARGUMENTS.join(', ')} are needed for create transfer" unless TRANSFER_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
9
|
+
|
10
|
+
asset_id = kwargs[:asset_id]
|
11
|
+
opponent_id = kwargs[:opponent_id]
|
12
|
+
amount = format('%.8f', kwargs[:amount].to_d.to_r).gsub(/\.?0+$/, '')
|
13
|
+
trace_id = kwargs[:trace_id] || SecureRandom.uuid
|
14
|
+
memo = kwargs[:memo] || ''
|
15
|
+
|
16
|
+
payload = {
|
17
|
+
asset_id:,
|
18
|
+
opponent_id:,
|
19
|
+
amount:,
|
20
|
+
trace_id:,
|
21
|
+
memo:
|
22
|
+
}
|
23
|
+
|
24
|
+
if pin.length > 6
|
25
|
+
pin_base64 = encrypt_tip_pin pin, 'TIP:TRANSFER:CREATE:', asset_id, opponent_id, amount, trace_id, memo
|
26
|
+
payload[:pin_base64] = pin_base64
|
27
|
+
else
|
28
|
+
encrypted_pin = encrypt_pin pin
|
29
|
+
payload[:pin] = encrypted_pin
|
30
|
+
end
|
31
|
+
|
32
|
+
path = '/transfers'
|
33
|
+
client.post path, **payload
|
34
|
+
end
|
35
|
+
|
36
|
+
def transfer(trace_id, access_token: nil)
|
37
|
+
path = format('/transfers/trace/%<trace_id>s', trace_id:)
|
38
|
+
client.get path, access_token:
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|