mixin_bot 0.12.1 → 1.1.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/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 +34 -56
- data/lib/mixin_bot/api/blaze.rb +4 -3
- data/lib/mixin_bot/api/conversation.rb +29 -49
- data/lib/mixin_bot/api/encrypted_message.rb +19 -19
- data/lib/mixin_bot/api/inscription.rb +71 -0
- data/lib/mixin_bot/api/legacy_collectible.rb +140 -0
- 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 +17 -222
- data/lib/mixin_bot/api/output.rb +48 -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 +12 -14
- 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 +295 -60
- data/lib/mixin_bot/api/transfer.rb +69 -31
- data/lib/mixin_bot/api/user.rb +88 -53
- data/lib/mixin_bot/api/withdraw.rb +52 -53
- data/lib/mixin_bot/api.rb +81 -46
- data/lib/mixin_bot/cli/api.rb +149 -5
- data/lib/mixin_bot/cli/utils.rb +14 -4
- data/lib/mixin_bot/cli.rb +13 -10
- data/lib/mixin_bot/client.rb +74 -127
- data/lib/mixin_bot/configuration.rb +98 -0
- data/lib/mixin_bot/nfo.rb +174 -0
- data/lib/mixin_bot/transaction.rb +524 -0
- data/lib/mixin_bot/utils/address.rb +121 -0
- data/lib/mixin_bot/utils/crypto.rb +218 -0
- data/lib/mixin_bot/utils/decoder.rb +56 -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 +77 -103
- data/lib/mixin_bot/api/collectible.rb +0 -138
- 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
@@ -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
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module LegacyCollectible
|
6
|
+
NFT_ASSET_MIXIN_ID = '1700941284a95f31b25ec8c546008f208f88eee4419ccdcdbe6e3195e60128ca'
|
7
|
+
|
8
|
+
def legacy_collectible(id, access_token: nil)
|
9
|
+
path = "/collectibles/tokens/#{id}"
|
10
|
+
client.get path, access_token:
|
11
|
+
end
|
12
|
+
|
13
|
+
def legacy_collection(id, access_token: nil)
|
14
|
+
path = "/collectibles/collections/#{id}"
|
15
|
+
client.get path, access_token:
|
16
|
+
end
|
17
|
+
|
18
|
+
def legacy_collectibles(**kwargs)
|
19
|
+
limit = kwargs[:limit] || 100
|
20
|
+
offset = kwargs[:offset] || ''
|
21
|
+
state = kwargs[:state] || ''
|
22
|
+
|
23
|
+
members = kwargs[:members] || [config.app_id]
|
24
|
+
threshold = kwargs[:threshold] || members.length
|
25
|
+
|
26
|
+
members = SHA3::Digest::SHA256.hexdigest(members&.sort&.join)
|
27
|
+
|
28
|
+
access_token = kwargs[:access_token]
|
29
|
+
|
30
|
+
path = '/collectibles/outputs'
|
31
|
+
params = {
|
32
|
+
limit:,
|
33
|
+
offset:,
|
34
|
+
state:,
|
35
|
+
members:,
|
36
|
+
threshold:
|
37
|
+
}
|
38
|
+
|
39
|
+
client.get path, **params, access_token:
|
40
|
+
end
|
41
|
+
|
42
|
+
COLLECTABLE_REQUEST_ACTIONS = %i[sign unlock].freeze
|
43
|
+
def create_collectible_request(action, raw, access_token: nil)
|
44
|
+
raise ArgumentError, "request action is limited in #{COLLECTABLE_REQUEST_ACTIONS.join(', ')}" unless COLLECTABLE_REQUEST_ACTIONS.include? action.to_sym
|
45
|
+
|
46
|
+
path = '/collectibles/requests'
|
47
|
+
payload = {
|
48
|
+
action:,
|
49
|
+
raw:
|
50
|
+
}
|
51
|
+
client.post path, **payload, access_token:
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_sign_collectible_request(raw, access_token: nil)
|
55
|
+
create_collectible_request 'sign', raw, access_token:
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_unlock_collectible_request(raw, access_token: nil)
|
59
|
+
create_collectible_request 'unlock', raw, access_token:
|
60
|
+
end
|
61
|
+
|
62
|
+
def sign_collectible_request(request_id, pin = nil)
|
63
|
+
pin ||= config.pin
|
64
|
+
raise ArgumentError, 'pin is needed for sign collectible request' if pin.blank?
|
65
|
+
|
66
|
+
path = format('/collectibles/requests/%<request_id>s/sign', request_id:)
|
67
|
+
payload =
|
68
|
+
if pin.length > 6
|
69
|
+
pin_base64 = encrypt_tip_pin(pin, 'TIP:COLLECTIBLE:REQUEST:SIGN:', request_id)
|
70
|
+
{
|
71
|
+
pin_base64:
|
72
|
+
}
|
73
|
+
else
|
74
|
+
{
|
75
|
+
pin: encrypt_pin(pin)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
client.post path, **payload
|
79
|
+
end
|
80
|
+
|
81
|
+
def unlock_collectible_request(request_id, pin = nil)
|
82
|
+
pin ||= config.pin
|
83
|
+
raise ArgumentError, 'pin is needed for sign collectible request' if pin.blank?
|
84
|
+
|
85
|
+
path = format('/collectibles/requests/%<request_id>s/unlock', request_id:)
|
86
|
+
payload =
|
87
|
+
if pin.length > 6
|
88
|
+
{
|
89
|
+
pin_base64: encrypt_tip_pin(pin, 'TIP:COLLECTIBLE:REQUEST:UNLOCK:', request_id)
|
90
|
+
}
|
91
|
+
else
|
92
|
+
{
|
93
|
+
pin: encrypt_pin(pin)
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
client.post path, **payload
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# collectible = {
|
102
|
+
# type: 'non_fungible_output',
|
103
|
+
# user_id: '',
|
104
|
+
# output_id: '',
|
105
|
+
# token_id: '',
|
106
|
+
# transaction_hash: '',
|
107
|
+
# output_index: '',
|
108
|
+
# amount: 1,
|
109
|
+
# senders: [],
|
110
|
+
# sender_threshold: 1,
|
111
|
+
# receivers: [],
|
112
|
+
# receivers_threshold: 1,
|
113
|
+
# state: 'unspent'
|
114
|
+
# }
|
115
|
+
COLLECTIBLE_TRANSACTION_ARGUMENTS = %i[collectible nfo receivers receivers_threshold].freeze
|
116
|
+
def build_collectible_transaction(**kwargs)
|
117
|
+
raise ArgumentError, "#{COLLECTIBLE_TRANSACTION_ARGUMENTS.join(', ')} are needed for build collectible transaction" unless COLLECTIBLE_TRANSACTION_ARGUMENTS.all? { |param| kwargs.keys.include? param }
|
118
|
+
|
119
|
+
kwargs = kwargs.with_indifferent_access
|
120
|
+
collectible = kwargs['collectible']
|
121
|
+
raise 'collectible is spent' if collectible['state'] == 'spent'
|
122
|
+
|
123
|
+
build_raw_transaction(
|
124
|
+
utxos: [collectible],
|
125
|
+
senders: collectible['receivers'],
|
126
|
+
senders_threshold: collectible['receivers_threshold'],
|
127
|
+
receivers: kwargs['receivers'],
|
128
|
+
receivers_threshold: kwargs['receivers_threshold'],
|
129
|
+
extra: [kwargs['nfo']].pack('H*'),
|
130
|
+
amount: 1,
|
131
|
+
asset_mixin_id: NFT_ASSET_MIXIN_ID,
|
132
|
+
hint: kwargs['hint']
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
def nft_memo(collection, token_id, meta)
|
137
|
+
MixinBot.utils.nft_memo collection, token_id, meta
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -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
|
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 legacy_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 legacy_outputs
|
27
|
+
alias multisig_outputs legacy_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
|
data/lib/mixin_bot/api/me.rb
CHANGED
@@ -3,37 +3,33 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Me
|
6
|
-
# https://developers.mixin.one/api/beta-mixin-message/read-profile/
|
7
6
|
def me(access_token: nil)
|
8
7
|
path = '/me'
|
9
|
-
|
10
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
11
|
-
client.get(path, headers: { 'Authorization': authorization })
|
8
|
+
client.get path, access_token:
|
12
9
|
end
|
13
|
-
alias read_me me
|
14
10
|
|
15
|
-
# https://developers.mixin.one/api/beta-mixin-message/update-profile/
|
16
11
|
# avatar_base64:
|
17
|
-
#
|
18
|
-
def update_me(
|
12
|
+
# String: Base64 of image, supports format png, jpeg and gif, base64 image size > 1024.
|
13
|
+
def update_me(**kwargs)
|
19
14
|
path = '/me'
|
20
15
|
payload = {
|
21
|
-
full_name: full_name,
|
22
|
-
avatar_base64: avatar_base64
|
16
|
+
full_name: kwargs[:full_name],
|
17
|
+
avatar_base64: kwargs[:avatar_base64],
|
18
|
+
access_token: kwargs[:access_token]
|
23
19
|
}
|
24
|
-
|
25
|
-
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
26
|
-
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
20
|
+
client.post path, **payload
|
27
21
|
end
|
28
22
|
|
29
23
|
# https://developers.mixin.one/api/beta-mixin-message/friends/
|
30
24
|
def friends(access_token: nil)
|
31
25
|
path = '/friends'
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
client.get path, access_token:
|
27
|
+
end
|
28
|
+
|
29
|
+
def safe_me(access_token: nil)
|
30
|
+
path = '/safe/me'
|
31
|
+
client.get path, access_token:
|
35
32
|
end
|
36
|
-
alias read_friends friends
|
37
33
|
end
|
38
34
|
end
|
39
35
|
end
|