crypto-service 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/crypto/base.rb +16 -39
- data/lib/crypto/resources/buy.rb +13 -32
- data/lib/crypto/resources/exchange.rb +79 -0
- data/lib/crypto/resources/exchanges/cx.rb +83 -0
- data/lib/crypto/resources/listing.rb +2 -0
- data/lib/crypto/resources/operation.rb +70 -0
- data/lib/crypto/resources/sell.rb +3 -19
- data/lib/crypto/resources/stop.rb +3 -23
- data/lib/crypto/version.rb +1 -1
- data/lib/crypto.rb +8 -0
- data/spec/fixtures/coinex/cx_balance_info.json +22 -0
- data/spec/fixtures/coinex/cx_error.json +6 -0
- data/spec/fixtures/coinex/cx_order_limit.json +7 -0
- data/spec/fixtures/coinex/cx_order_market.json +29 -0
- data/spec/fixtures/coinpaprika/coinpaprika_exchanges.json +2655 -0
- data/spec/fixtures/coinpaprika/coinpaprika_info.json +443 -0
- data/spec/fixtures/crypto_listing_info.json +4037 -0
- data/spec/fixtures/crypto_listing_info_fail.json +18 -0
- data/spec/fixtures/crypto_top_info.json +4245 -0
- data/spec/fixtures/gecko/gecko_coin_exchanges.json +4860 -0
- data/spec/fixtures/gecko/gecko_coin_info.json +64007 -0
- data/spec/fixtures/gecko/gecko_coin_info_fail.json +64002 -0
- data/spec/fixtures/test/crypto_buy.json +44 -0
- data/spec/fixtures/test/crypto_listing.json +33 -0
- metadata +76 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be4d060ae79ae7ee1ae22306b5fc62d91b89acda8265b287fbc7d2b0858acfa4
|
4
|
+
data.tar.gz: 4e1948a84ee2ab439c41b9ab15f30bb357ba3ac0214bc0d6a0a4ab3b2d7b4439
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aea9190bcaeeb1fac078afbc71e0597496fc6b05b4509a458437b6a7f09b43d0e5af80c0b97d4660e57e487d3d7df1a61d9ac35270258a44ea542161ba9a4f5a
|
7
|
+
data.tar.gz: 8b557573f59ba140d4f30842ec2685ea76b970beeaeb99a8c13286232607647a9af709a3b6acd7b4e7251f56cfc068319b52ba2db6b33a2af03950cbd4b273b1
|
data/lib/crypto/base.rb
CHANGED
@@ -3,39 +3,25 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'net/http'
|
5
5
|
require 'json'
|
6
|
+
|
6
7
|
require 'coinex'
|
8
|
+
require 'coinpaprika'
|
9
|
+
require 'gecko'
|
10
|
+
require 'string/similarity'
|
7
11
|
require 'request'
|
8
12
|
|
9
13
|
module Crypto
|
10
14
|
class Base
|
11
15
|
class << self
|
12
|
-
attr_accessor :app_info, :path_prefix, :development
|
13
|
-
|
14
|
-
def calculate_win_or_lost(avg_price:, exchange:, settings:)
|
15
|
-
percentages = percentage(exchange: exchange, settings: settings)
|
16
|
-
profit = percentages.first
|
17
|
-
lost = percentages[1]
|
18
|
-
|
19
|
-
win_price = avg_price.to_f + ((avg_price.to_f * profit.to_f) / 100)
|
20
|
-
lost_price = avg_price.to_f - ((avg_price.to_f * lost.to_f) / 100)
|
21
|
-
|
22
|
-
[win_price, lost_price]
|
23
|
-
end
|
16
|
+
attr_accessor :app_info, :path_prefix, :development, :testing
|
24
17
|
|
25
|
-
def
|
26
|
-
|
18
|
+
def test(fixture)
|
19
|
+
load_fixture(fixture)
|
27
20
|
end
|
28
21
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
loss = settings[:perc_lost_exchange]
|
33
|
-
else
|
34
|
-
profit = settings[:perc_profit_all]
|
35
|
-
loss = settings[:perc_lost_all]
|
36
|
-
end
|
37
|
-
|
38
|
-
[profit, loss]
|
22
|
+
def load_fixture(fixture)
|
23
|
+
fixture_path = File.expand_path(File.join(__dir__, '../../spec', 'fixtures/test', fixture))
|
24
|
+
JSON.parse(File.read(fixture_path))
|
39
25
|
end
|
40
26
|
|
41
27
|
def correct(result)
|
@@ -46,27 +32,18 @@ module Crypto
|
|
46
32
|
{ 'status' => :failed, 'code' => result['code'], 'message' => result['message'] }
|
47
33
|
end
|
48
34
|
|
49
|
-
def
|
50
|
-
|
51
|
-
Crypto::Exchanges::Cx.secret_key(settings[:secret_key])
|
52
|
-
end
|
53
|
-
|
54
|
-
def response_correct?(result)
|
55
|
-
(result['code']).zero?
|
35
|
+
def binance?(exchanges)
|
36
|
+
exchanges.include?('Binance')
|
56
37
|
end
|
57
38
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
params[:tonce] = Crypto.development ? '1672669323219' : data_signature[:tonce]
|
62
|
-
params[:signature] = data_signature[:signature]
|
63
|
-
|
64
|
-
params
|
39
|
+
def coinex?(exchanges)
|
40
|
+
exchanges.include?('CoinEx')
|
65
41
|
end
|
66
42
|
end
|
67
43
|
|
68
44
|
self.app_info = ::ENV['APP_INFO'] || "#{::Crypto::NAME} V#{::Crypto.version}"
|
69
45
|
self.path_prefix = 'https://www.binance.com/bapi/composite/v1/public/promo/cmc/cryptocurrency'
|
70
|
-
self.development = false
|
46
|
+
self.development = false # For rspec & test
|
47
|
+
self.testing = false # For real call, but return development payload
|
71
48
|
end
|
72
49
|
end
|
data/lib/crypto/resources/buy.rb
CHANGED
@@ -7,48 +7,29 @@
|
|
7
7
|
# Exchange: exchange where crypto is listed or buy, (Binance → 20%, Other exchange → 10% )
|
8
8
|
|
9
9
|
module Crypto
|
10
|
-
class Buy <
|
10
|
+
class Buy < Operation
|
11
11
|
class << self
|
12
12
|
# rubocop: disable Metrics/MethodLength, Lint/MissingCopEnableDirective
|
13
|
-
def order(
|
14
|
-
|
15
|
-
result = Coinex::Order.market(params: params(
|
16
|
-
symbol: payload['symbol'],
|
17
|
-
count: count
|
18
|
-
))
|
19
|
-
|
20
|
-
Crypto::Exchanges::Cx.clean_keys
|
21
|
-
|
22
|
-
if response_correct?(result)
|
23
|
-
prices = calculate_win_or_lost(avg_price: result['data']['avg_price'], exchange: exchange, settings: settings)
|
24
|
-
divert(payload: payload, result: result, prices: prices, settings: settings)
|
25
|
-
else
|
26
|
-
error(result)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def balance(count)
|
31
|
-
params = Coinex::Signature.calculate
|
32
|
-
params[:tonce] = '1672669323219' if Crypto.development
|
13
|
+
def order(data)
|
14
|
+
return test('crypto_buy.json') if Crypto.testing
|
33
15
|
|
34
|
-
|
35
|
-
balance['data']['USDT']['available'].to_i / count.to_i
|
16
|
+
operations(data: data, index: nil)
|
36
17
|
end
|
37
18
|
|
38
|
-
|
39
|
-
params = { market: "#{symbol}USDT", type: 'buy', amount: balance(count) }
|
40
|
-
calculate_signature(params)
|
41
|
-
end
|
19
|
+
private
|
42
20
|
|
43
|
-
def divert(
|
21
|
+
def divert(data)
|
44
22
|
operations = []
|
45
|
-
|
23
|
+
data[:result]['operation'] = 'B'
|
24
|
+
operations << data[:result]
|
46
25
|
|
47
|
-
loss = Crypto::Loss.limit(
|
26
|
+
loss = Crypto::Loss.limit(data: data, index: 0)
|
27
|
+
loss['operation'] = 'SL'
|
48
28
|
operations << loss
|
49
|
-
return operations if payload
|
29
|
+
return operations if data.dig(:payload, :mode) == :top
|
50
30
|
|
51
|
-
profit = Crypto::Profit.limit(
|
31
|
+
profit = Crypto::Profit.limit(data: data, index: 1)
|
32
|
+
profit['operation'] = 'TP'
|
52
33
|
operations << profit
|
53
34
|
|
54
35
|
operations
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class that return exchanges of a crypto
|
4
|
+
|
5
|
+
module Crypto
|
6
|
+
class Exchange < Base
|
7
|
+
PERCENTAGE = 70
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def info(symbol:, name:)
|
11
|
+
exchanges = gecko(symbol: symbol, name: name)
|
12
|
+
return exchanges unless exchanges.nil?
|
13
|
+
|
14
|
+
coinpaprika(symbol: symbol, name: name)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def exchange(symbol:, service:, name:)
|
20
|
+
payload = Object.const_get(service).info(symbol)
|
21
|
+
return nil if payload.empty?
|
22
|
+
|
23
|
+
if hash?(payload) || (array?(payload) && payload.size <= 1)
|
24
|
+
return simple_exchange(service: service,
|
25
|
+
payload: payload)
|
26
|
+
end
|
27
|
+
|
28
|
+
# If array & size > 1, calculate max similar string
|
29
|
+
max_similarity(name: name, service: service, payload: payload)
|
30
|
+
end
|
31
|
+
|
32
|
+
def gecko(symbol:, name:)
|
33
|
+
exchange(symbol: symbol, service: 'Gecko::Coin', name: name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def coinpaprika(symbol:, name:)
|
37
|
+
exchange(symbol: symbol, service: 'Coinpaprika::Coin', name: name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def hash?(payload)
|
41
|
+
payload.instance_of?(::Hash)
|
42
|
+
end
|
43
|
+
|
44
|
+
def array?(payload)
|
45
|
+
payload.instance_of?(::Array)
|
46
|
+
end
|
47
|
+
|
48
|
+
def simple_exchange(service:, payload:)
|
49
|
+
if array?(payload)
|
50
|
+
Object.const_get(service).exchanges(payload.first['id'])
|
51
|
+
else
|
52
|
+
Object.const_get(service).exchanges(payload[:id])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def max_similarity(name:, service:, payload:)
|
57
|
+
similarity = calculate_similarity(name: name, payload: payload)
|
58
|
+
similarity = similarity.sort_by { |h| h[:percentage] }.reverse
|
59
|
+
|
60
|
+
crypto = similarity.select { |x| (x[:percentage] * 100).to_i > PERCENTAGE }.first
|
61
|
+
return nil if similarity.nil? || similarity.empty?
|
62
|
+
|
63
|
+
simple_exchange(service: service, payload: crypto)
|
64
|
+
end
|
65
|
+
|
66
|
+
def calculate_similarity(name:, payload:)
|
67
|
+
similarity = []
|
68
|
+
payload.each do |p|
|
69
|
+
similarity.push(
|
70
|
+
'id': p['id'],
|
71
|
+
'percentage': String::Similarity.cosine(name, p['name'])
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
similarity
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -28,6 +28,89 @@ module Crypto
|
|
28
28
|
def ping
|
29
29
|
Coinex.ping == { status: 200 }
|
30
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# rubocop: disable Lint/MissingCopEnableDirective
|
35
|
+
def order(data:, index: nil)
|
36
|
+
if data[:result].nil? && data[:prices].nil? # Buy
|
37
|
+
result = Coinex::Order.market(params: params(data: data, index: index))
|
38
|
+
return correct(result) if cex_response_correct?(result) && index == 2
|
39
|
+
|
40
|
+
order_market(data: data, result: result)
|
41
|
+
else # Limit (SL & TP)
|
42
|
+
result = Coinex::Order.limit(params: params(data: data, index: index))
|
43
|
+
order_limit(result: result)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# rubocop: disable Metrics/MethodLength
|
48
|
+
def order_market(data:, result:)
|
49
|
+
if cex_response_correct?(result) # If correct, call SL & TP
|
50
|
+
prices = Crypto::Operation.send :calculate_win_or_lost,
|
51
|
+
avg_price: result['data']['avg_price'],
|
52
|
+
exchanges: data[:exchanges],
|
53
|
+
settings: data[:settings]
|
54
|
+
|
55
|
+
data[:prices] = prices
|
56
|
+
data[:result] = result
|
57
|
+
Crypto::Buy.send :divert, data
|
58
|
+
else
|
59
|
+
error(result)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def order_limit(result:)
|
64
|
+
if cex_response_correct?(result)
|
65
|
+
correct(result)
|
66
|
+
else
|
67
|
+
error(result)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def balance(count)
|
72
|
+
params = Coinex::Signature.calculate
|
73
|
+
params[:tonce] = '1672669323219' if Crypto.development
|
74
|
+
|
75
|
+
balance = Coinex::Balance.info(params: params)
|
76
|
+
balance['data']['USDT']['available'].to_i / count.to_i
|
77
|
+
end
|
78
|
+
|
79
|
+
def params(data:, index: nil)
|
80
|
+
params = if index.nil?
|
81
|
+
{ market: "#{data.dig(:payload, :symbol)}USDT", type: 'buy', amount: balance(data[:count]) }
|
82
|
+
else
|
83
|
+
limit_params(data: data, index: index)
|
84
|
+
end
|
85
|
+
|
86
|
+
calculate_signature(params)
|
87
|
+
end
|
88
|
+
|
89
|
+
def limit_params(data:, index:)
|
90
|
+
case index
|
91
|
+
when 0 # SL
|
92
|
+
{ market: "#{data.dig(:payload, :symbol)}USDT", type: 'sell', amount: data.dig(:result, 'data', 'amount'),
|
93
|
+
stop_price: Crypto::Loss.send(:what_price, data[:prices]) }
|
94
|
+
when 1 # TP
|
95
|
+
{ market: "#{data.dig(:payload, :symbol)}USDT", type: 'sell', amount: data.dig(:result, 'data', 'amount'),
|
96
|
+
stop_price: Crypto::Profit.send(:what_price, data[:prices]) }
|
97
|
+
when 2 # SELL
|
98
|
+
{ market: "#{data.dig(:payload, :symbol)}USDT", type: 'sell', amount: data[:balance] }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def calculate_signature(params)
|
103
|
+
data_signature = Coinex::Signature.calculate(params: params)
|
104
|
+
|
105
|
+
params[:tonce] = Crypto.development ? '1672669323219' : data_signature[:tonce]
|
106
|
+
params[:signature] = data_signature[:signature]
|
107
|
+
|
108
|
+
params
|
109
|
+
end
|
110
|
+
|
111
|
+
def cex_response_correct?(result)
|
112
|
+
(result['code']).zero?
|
113
|
+
end
|
31
114
|
end
|
32
115
|
end
|
33
116
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Crypto
|
4
|
+
class Operation < Base
|
5
|
+
class << self
|
6
|
+
private
|
7
|
+
|
8
|
+
def setup_keys(exchanges:, settings:)
|
9
|
+
return unless coinex?(exchanges)
|
10
|
+
|
11
|
+
coinex_keys(settings)
|
12
|
+
'CoinEx'
|
13
|
+
end
|
14
|
+
|
15
|
+
def coinex_keys(settings)
|
16
|
+
Crypto::Exchanges::Cx.access_id(settings[:access_id])
|
17
|
+
Crypto::Exchanges::Cx.secret_key(settings[:secret_key])
|
18
|
+
end
|
19
|
+
|
20
|
+
def clean_keys(exchange)
|
21
|
+
first = exchange.chr.upcase
|
22
|
+
last = exchange[-1].downcase
|
23
|
+
|
24
|
+
class_send = "Crypto::Exchanges::#{first}#{last}"
|
25
|
+
Object.const_get(class_send).clean_keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def operations(data:, index: nil)
|
29
|
+
exchange = setup_keys(exchanges: data[:exchanges], settings: data[:settings])
|
30
|
+
return nil if exchange.nil?
|
31
|
+
|
32
|
+
# Buy or TP or SL
|
33
|
+
result = buy_or_limit(data: data, exchange: exchange, index: index)
|
34
|
+
clean_keys(exchange)
|
35
|
+
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def buy_or_limit(data:, exchange:, index:)
|
40
|
+
case exchange
|
41
|
+
when 'CoinEx'
|
42
|
+
Crypto::Exchanges::Cx.send(:order, data: data, index: index)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def calculate_win_or_lost(avg_price:, exchanges:, settings:)
|
47
|
+
percentages = percentage(exchanges: exchanges, settings: settings)
|
48
|
+
profit = percentages.first
|
49
|
+
lost = percentages[1]
|
50
|
+
|
51
|
+
win_price = avg_price.to_f + ((avg_price.to_f * profit.to_f) / 100)
|
52
|
+
lost_price = avg_price.to_f - ((avg_price.to_f * lost.to_f) / 100)
|
53
|
+
|
54
|
+
[win_price, lost_price]
|
55
|
+
end
|
56
|
+
|
57
|
+
def percentage(exchanges:, settings:)
|
58
|
+
if binance?(exchanges)
|
59
|
+
profit = settings[:perc_profit_exchange]
|
60
|
+
loss = settings[:perc_lost_exchange]
|
61
|
+
else
|
62
|
+
profit = settings[:perc_profit_all]
|
63
|
+
loss = settings[:perc_lost_all]
|
64
|
+
end
|
65
|
+
|
66
|
+
[profit, loss]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,26 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Crypto
|
4
|
-
class Sell <
|
4
|
+
class Sell < Operation
|
5
5
|
class << self
|
6
|
-
def order(
|
7
|
-
|
8
|
-
result = Coinex::Order.market(params: params(payload))
|
9
|
-
|
10
|
-
Crypto::Exchanges::Cx.clean_keys
|
11
|
-
|
12
|
-
if response_correct?(result)
|
13
|
-
correct(result)
|
14
|
-
else
|
15
|
-
error(result)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def params(payload)
|
22
|
-
params = { market: "#{payload['symbol']}USDT", type: 'sell', amount: payload['balance'] }
|
23
|
-
calculate_signature(params)
|
6
|
+
def order(data)
|
7
|
+
operations(data: data, index: 2)
|
24
8
|
end
|
25
9
|
end
|
26
10
|
end
|
@@ -1,30 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Crypto
|
4
|
-
class Stop <
|
4
|
+
class Stop < Operation
|
5
5
|
class << self
|
6
|
-
def limit(
|
7
|
-
|
8
|
-
|
9
|
-
result = Coinex::Order.limit(params: params(
|
10
|
-
payload: payload, result: result, prices: prices
|
11
|
-
))
|
12
|
-
|
13
|
-
Crypto::Exchanges::Cx.clean_keys
|
14
|
-
if response_correct?(result)
|
15
|
-
correct(result)
|
16
|
-
else
|
17
|
-
error(result)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def params(payload:, result:, prices:)
|
24
|
-
params = { market: "#{payload['symbol']}USDT", type: 'sell', amount: result['data']['amount'],
|
25
|
-
stop_price: what_price(prices) }
|
26
|
-
|
27
|
-
calculate_signature(params)
|
6
|
+
def limit(data:, index:)
|
7
|
+
operations(data: data, index: index)
|
28
8
|
end
|
29
9
|
end
|
30
10
|
end
|
data/lib/crypto/version.rb
CHANGED
data/lib/crypto.rb
CHANGED
@@ -35,6 +35,14 @@ module Crypto
|
|
35
35
|
def development
|
36
36
|
@mutex.synchronize { ::Crypto::Base.development }
|
37
37
|
end
|
38
|
+
|
39
|
+
def testing=(boolean)
|
40
|
+
@mutex.synchronize { ::Crypto::Base.testing = boolean }
|
41
|
+
end
|
42
|
+
|
43
|
+
def testing
|
44
|
+
@mutex.synchronize { ::Crypto::Base.testing }
|
45
|
+
end
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"code": 0,
|
3
|
+
"data": {
|
4
|
+
"BTC": {
|
5
|
+
"available": "9080.84156954",
|
6
|
+
"frozen": "1"
|
7
|
+
},
|
8
|
+
"USDT": {
|
9
|
+
"available" : "43724611.65252752",
|
10
|
+
"frozen": "1897.84472856"
|
11
|
+
},
|
12
|
+
"CET": {
|
13
|
+
"available": "6524.13695056",
|
14
|
+
"frozen": "0"
|
15
|
+
},
|
16
|
+
"ETH": {
|
17
|
+
" available": "10000",
|
18
|
+
"frozen": "0"
|
19
|
+
}
|
20
|
+
},
|
21
|
+
"message": "Success"
|
22
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
{
|
2
|
+
"code": 0,
|
3
|
+
"data": {
|
4
|
+
"id": 35435973,
|
5
|
+
"create_time": 1636020409,
|
6
|
+
"finished_time": null,
|
7
|
+
"amount": 43724611,
|
8
|
+
"price": " 0",
|
9
|
+
"deal_amount": "1",
|
10
|
+
"deal_money": "54334.4024000000000000",
|
11
|
+
"deal_fee": "0",
|
12
|
+
"stock_fee": "0",
|
13
|
+
"money_fee": "0",
|
14
|
+
" asset_fee": "30.427265344000000000000000",
|
15
|
+
"fee_asset": "CET",
|
16
|
+
"fee_discount": "0.70",
|
17
|
+
"avg_price": "54334.4024",
|
18
|
+
"market": "BTCUSDT",
|
19
|
+
"left": "0.00000000 ",
|
20
|
+
"maker_fee_rate": "0",
|
21
|
+
"taker_fee_rate": "0.0016",
|
22
|
+
"order_type": "market",
|
23
|
+
"type": "sell",
|
24
|
+
"status": "done",
|
25
|
+
"client_id ": "",
|
26
|
+
"source_id": "1234"
|
27
|
+
},
|
28
|
+
"message": "Success"
|
29
|
+
}
|