taunchpad 3.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +32 -0
  4. data/app/controllers/concerns/exception_handlers.rb +19 -0
  5. data/app/controllers/concerns/jwt_payload.rb +19 -0
  6. data/app/controllers/concerns/response.rb +25 -0
  7. data/app/controllers/launchpad/api/v2/admin/base_controller.rb +22 -0
  8. data/app/controllers/launchpad/api/v2/admin/ieo/orders_controller.rb +29 -0
  9. data/app/controllers/launchpad/api/v2/admin/ieo/sales_controller.rb +112 -0
  10. data/app/controllers/launchpad/api/v2/private/base_controller.rb +14 -0
  11. data/app/controllers/launchpad/api/v2/private/ieo/orders_controller.rb +97 -0
  12. data/app/controllers/launchpad/api/v2/private/ieo/sales_controller.rb +22 -0
  13. data/app/controllers/launchpad/api/v2/public/base_controller.rb +13 -0
  14. data/app/controllers/launchpad/api/v2/public/ieo/sales_controller.rb +56 -0
  15. data/app/controllers/launchpad/application_controller.rb +10 -0
  16. data/app/helpers/launchpad/application_helper.rb +4 -0
  17. data/app/models/launchpad/application_record.rb +5 -0
  18. data/app/models/launchpad/ieo.rb +21 -0
  19. data/app/models/launchpad/ieo/order.rb +576 -0
  20. data/app/models/launchpad/ieo/sale.rb +371 -0
  21. data/app/models/launchpad/ieo/sale_pair.rb +83 -0
  22. data/app/services/barong/management_api_v2/client.rb +33 -0
  23. data/app/services/management_api_v2/client.rb +73 -0
  24. data/app/services/management_api_v2/exception.rb +25 -0
  25. data/app/services/peatio/management_api_v2/client.rb +49 -0
  26. data/app/workers/launchpad/ieo/order_execute_worker.rb +26 -0
  27. data/app/workers/launchpad/ieo/order_refund_worker.rb +19 -0
  28. data/app/workers/launchpad/ieo/order_release_worker.rb +22 -0
  29. data/app/workers/launchpad/ieo/sale_cancel_worker.rb +20 -0
  30. data/app/workers/launchpad/ieo/sale_currency_list_worker.rb +19 -0
  31. data/app/workers/launchpad/ieo/sale_distribute_worker.rb +20 -0
  32. data/app/workers/launchpad/ieo/sale_finish_worker.rb +21 -0
  33. data/app/workers/launchpad/ieo/sale_pair_list_worker.rb +21 -0
  34. data/app/workers/launchpad/ieo/sale_release_funds_worker.rb +23 -0
  35. data/app/workers/launchpad/ieo/sale_start_worker.rb +21 -0
  36. data/config/initializers/active_model.rb +13 -0
  37. data/config/initializers/api_pagination.rb +33 -0
  38. data/config/initializers/inflections.rb +19 -0
  39. data/config/routes.rb +35 -0
  40. data/db/migrate/20191120145404_create_launchpad_ieo.rb +52 -0
  41. data/db/migrate/20200814114105_add_fees_policy_in_sale.rb +5 -0
  42. data/lib/launchpad.rb +10 -0
  43. data/lib/launchpad/engine.rb +17 -0
  44. data/lib/launchpad/precision_validator.rb +25 -0
  45. data/lib/launchpad/version.rb +3 -0
  46. data/lib/tasks/launchpad_tasks.rake +4 -0
  47. metadata +229 -0
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Launchpad
5
+ module API
6
+ module V2
7
+ module Public
8
+ class BaseController < ApplicationController
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "launchpad/api/v2/public/base_controller"
4
+ module Launchpad
5
+ module API
6
+ module V2
7
+ module Public
8
+ module IEO
9
+ class SalesController < BaseController
10
+ def index
11
+ ransack_params = {
12
+ state_in: params[:state],
13
+ currency_id_eq: params[:currency_id],
14
+ name_cont_all: params[:name]
15
+ }
16
+
17
+ sales = Launchpad::IEO::Sale.public_states.order(created_at: (params[:ordering] || :asc))
18
+ .ransack(ransack_params)
19
+
20
+ json_response(paginate(sales.result), 200)
21
+ end
22
+
23
+ def show
24
+ launchpad = Launchpad::IEO::Sale.find(params[:id])
25
+ additional_params = {
26
+ generic_bids: launchpad.orders.where("state = ? OR state = ?", 'active', 'completed').last(50).as_json(uid: true),
27
+ }
28
+ json_response(launchpad.as_json.merge(additional_params), 200)
29
+ end
30
+
31
+ def search
32
+ ransack_params = {
33
+ state_in: params[:state],
34
+ currency_id_eq: params[:currency_id],
35
+ name_cont_all: params[:name]
36
+ }
37
+
38
+ sales = Launchpad::IEO::Sale.includes(:pairs)
39
+ .public_states.order(Arel.sql("CASE state
40
+ WHEN 'ongoing' THEN '1'
41
+ WHEN 'preparing' THEN '2'
42
+ WHEN 'distributing' THEN '3'
43
+ WHEN 'refunding' THEN '4'
44
+ WHEN 'finished' THEN '5'
45
+ WHEN 'cancelled' THEN '6'
46
+ END")).order(created_at: :desc)
47
+ .ransack(ransack_params)
48
+
49
+ json_response(paginate(sales.result).map(&:sales_to_json), 200)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Launchpad
4
+ class ApplicationController < ActionController::Base
5
+ skip_before_action :verify_authenticity_token
6
+ include Rails::Pagination
7
+ include Response
8
+ include ExceptionHandlers
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module Launchpad
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module Launchpad
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module Launchpad
2
+ module IEO
3
+ TOKENS_AMOUNT_PRECISION = 4
4
+ RATIO_PRECISION = 4
5
+ COMMISSION_PRECISION = 6
6
+
7
+ LIABILITY_CODES = {
8
+ fiat: { main: 201, locked: 211 },
9
+ coin: { main: 202, locked: 212 }
10
+ }
11
+
12
+ REVENUE_CODES = {
13
+ fiat: { main: 301 },
14
+ coin: { main: 302 }
15
+ }
16
+
17
+ def self.table_name_prefix
18
+ 'ieo_'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,576 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Launchpad
4
+ module IEO
5
+ # TODO: Add validation that orders are created only for ongoing sales.
6
+ # TODO: Add attr_readonly for all models.
7
+ class Order < ApplicationRecord
8
+ # == Constants ============================================================
9
+
10
+ include AASM
11
+
12
+ # == Attributes ===========================================================
13
+
14
+ # == Extensions ===========================================================
15
+
16
+ serialize :transfer_keys, Array
17
+
18
+ # NOTE: We use pessimistic locking for avoiding double transfer creation.
19
+ aasm requires_lock: true, column: :state do
20
+ state :preparing, initial: true
21
+ state :rejected
22
+ state :active
23
+ state :refunding
24
+ state :closed
25
+ state :cancelled
26
+ state :executing
27
+ state :completed
28
+ state :releasing
29
+ state :purchased
30
+
31
+ # Events docs:
32
+ # preparing -> active;
33
+ # preparing -> rejected;
34
+ # active -> executing;
35
+ # active -> refunfing;
36
+ # active -> cancelled;
37
+ # exucuting -> purchased;
38
+ # executing -> completed;
39
+ # executing -> refunding;
40
+ # executing -> cancelled;
41
+ # purchased -> releasing;
42
+ # releasing -> completed;
43
+ # refunding -> closed;
44
+
45
+ # dispatch! => preparing -> active;
46
+ #
47
+ # reject! => preparing -> rejected;
48
+ #
49
+ # execute! => active -> executing;
50
+ #
51
+ # - => executing -> purchased;
52
+ # /
53
+ # purchase! - - => executing -> completed;
54
+ # \
55
+ # - => executing -> refunding;
56
+ #
57
+ # refund! => active -> refunding;
58
+ #
59
+ # release! => purchased -> releasing;
60
+ #
61
+ # unlock! => releasing -> completed;
62
+ #
63
+ # close! => refunding -> closed;
64
+ #
65
+ # - => executing -> cancelled;
66
+ # /
67
+ # cancel! - |
68
+ # \
69
+ # - => active -> cancelled;
70
+ #
71
+
72
+ event :dispatch do
73
+ transitions from: :preparing, to: :active do
74
+ after do
75
+ perform_transfer(lock_transfer_params)
76
+ end
77
+ end
78
+ after_commit do
79
+ sale.notify_ranger
80
+ execute! if sale.type.fcfs?
81
+ end
82
+ end
83
+
84
+ event :reject do
85
+ transitions from: :preparing, to: :rejected
86
+ end
87
+
88
+ event :execute, after_commit: :enqueue_execute_job do
89
+ transitions from: :active, to: :executing
90
+ end
91
+
92
+ event :purchase do
93
+ transitions from: :executing, to: :purchased do
94
+ guard do
95
+ calculate_share
96
+ sale.lockup_percentage.positive?
97
+ end
98
+ after do
99
+ perform_transfer(execute_transfer_params)
100
+ end
101
+ end
102
+
103
+ transitions from: :executing, to: :completed do
104
+ guard do
105
+ calculate_share
106
+ sale.lockup_percentage.zero?
107
+ end
108
+ after do
109
+ perform_transfer(execute_transfer_params)
110
+ end
111
+ end
112
+
113
+ transitions from: :executing, to: :refunding
114
+
115
+ after_commit do
116
+ sale.finish! if sale.type.fcfs? && sale.full?
117
+ enqueue_refund_job if refunding?
118
+ end
119
+ end
120
+
121
+ event :refund do
122
+ transitions from: :active, to: :refunding
123
+
124
+ after_commit do
125
+ enqueue_refund_job
126
+ end
127
+ end
128
+
129
+ event :release do
130
+ transitions from: :purchased, to: :releasing
131
+
132
+ after_commit do
133
+ enqueue_released_job
134
+ end
135
+ end
136
+
137
+ event :unlock do
138
+ transitions from: :releasing, to: :completed do
139
+ after do
140
+ perform_transfer(release_transfer_params)
141
+ calculate_release
142
+ end
143
+ end
144
+ end
145
+
146
+ event :close do
147
+ transitions from: :refunding, to: :closed do
148
+ after do
149
+ calculate_refunded
150
+ perform_transfer(cancel_transfer_params)
151
+ end
152
+ end
153
+ end
154
+
155
+ event :cancel do
156
+ transitions from: :executing, to: :cancelled do
157
+ after do
158
+ calculate_refund
159
+ Rails.logger.info("Transition from executing to cancelled")
160
+ perform_transfer(cancel_transfer_params)
161
+ end
162
+ end
163
+
164
+ transitions from: :active, to: :cancelled do
165
+ after do
166
+ calculate_refund
167
+ Rails.logger.info("Transition from active to cancelled")
168
+ perform_transfer(cancel_transfer_params)
169
+ end
170
+ end
171
+
172
+ after_commit do
173
+ sale.notify_ranger
174
+ end
175
+ end
176
+ end
177
+
178
+ # == Relationships ========================================================
179
+
180
+ belongs_to :sale_pair, class_name: "Launchpad::IEO::SalePair", required: true
181
+
182
+ has_one :sale, class_name: "Launchpad::IEO::Sale", through: :sale_pair
183
+
184
+ # == Validations ==========================================================
185
+
186
+ validates :uid, :contribution, :executed, :refunded, :tokens_received,
187
+ :commission_rate, :commission_amount, :state, presence: {
188
+ message: ->(_, _data) { "missing_" }
189
+ }
190
+
191
+ validates :contribution,
192
+ numericality: {
193
+ greater_than: 0,
194
+ message: ->(_, _data) { "non_positive_" }
195
+ }
196
+
197
+ validates :executed, :refunded, :tokens_received,
198
+ :commission_rate, :commission_amount,
199
+ numericality: {
200
+ greater_than_or_equal_to: 0,
201
+ message: ->(_, _data) { "negative_" }
202
+ }
203
+
204
+ validates :tokens_ordered,
205
+ numericality: {
206
+ greater_than_or_equal_to: ->(order) { order.sale.min_amount },
207
+ message: ->(_, _data) { "_less_than_min_amount" }
208
+ }
209
+
210
+ validates :tokens_ordered,
211
+ numericality: {
212
+ less_than_or_equal_to: ->(order) { order.sale.max_amount - order.sale.orders.active_and_prepared.where(uid: order.uid).tokens_ordered },
213
+ message: ->(_, _data) { "_greater_than_max_amount" }
214
+ }, if: ->(order) { order.sale.type.proportional? },
215
+ on: :create
216
+
217
+ validates :tokens_ordered,
218
+ numericality: {
219
+ less_than_or_equal_to: ->(order) { order.sale.max_amount - order.sale.orders.active_and_prepared_and_completed.where(uid: order.uid).tokens_ordered },
220
+ message: ->(_, _data) { "_greater_than_max_amount" }
221
+ }, if: ->(order) { order.sale.type.fcfs? },
222
+ on: :create
223
+
224
+ validates :tokens_ordered,
225
+ numericality: {
226
+ less_than_or_equal_to: ->(order) { order.sale.supply - order.sale.orders.executable.tokens_ordered },
227
+ message: ->(_, _) { "_greater_than_sale_supply" }
228
+ }, if: ->(order) { order.sale.type.fcfs? },
229
+ on: :create
230
+
231
+ # == Scopes ===============================================================
232
+
233
+ scope :active_and_prepared, -> { where(state: %i[active preparing]) }
234
+
235
+ scope :active_and_prepared_and_completed, -> { where(state: %i[active preparing purchased completed]) }
236
+
237
+ # Scope represents all orders which may be executed.
238
+ # So this orders influence tokens_ordered and ratio.
239
+ scope :executable, -> { where(state: %i[preparing active executing purchased completed]) }
240
+
241
+ # == Callbacks ============================================================
242
+
243
+ before_validation(on: :create) { self.commission_rate = sale.commission }
244
+
245
+ # == Class Methods ========================================================
246
+
247
+ class << self
248
+ def tokens_ordered(ndigits: Launchpad::IEO::TOKENS_AMOUNT_PRECISION)
249
+ # Include SalePair for accessing price.
250
+ if first&.add?
251
+ all.includes(:sale_pair).sum("contribution / ieo_sale_pairs.price").round(ndigits).to_d
252
+ else
253
+ all.includes(:sale_pair).sum("(1 - commission_rate) * contribution / ieo_sale_pairs.price").round(ndigits).to_d
254
+ end
255
+ end
256
+
257
+ def release_fund(percent = 0)
258
+ return if percent.to_d == 0
259
+
260
+ all.each do |order|
261
+ next unless order.tokens_locked.positive?
262
+
263
+ funds = (order.tokens_locked + order.tokens_received) * percent.to_d
264
+ order.perform_transfer(order.release_funds_params(funds))
265
+ order.tokens_locked -= funds
266
+ order.tokens_received += funds
267
+ order.save!
268
+ end
269
+ end
270
+ end
271
+
272
+ # == Instance Methods =====================================================
273
+
274
+ delegate :price, to: :sale_pair
275
+
276
+ def as_json(_options={})
277
+ if _options[:uid]
278
+ super(except: [:transfer_keys, :uid],
279
+ methods: %i[tokens_ordered base_currency quote_currency ratio sale_name]).merge(sale_id: sale.id)
280
+ else
281
+ super(except: :transfer_keys,
282
+ methods: %i[tokens_ordered base_currency quote_currency ratio sale_name]).merge(sale_id: sale.id)
283
+ end
284
+ end
285
+
286
+ def tokens_ordered(ndigits: Launchpad::IEO::TOKENS_AMOUNT_PRECISION)
287
+ if add?
288
+ (contribution / price).round(ndigits)
289
+ else
290
+ ((1 - commission_rate) * contribution / price).round(ndigits)
291
+ end
292
+ end
293
+
294
+ def executed_without_commission
295
+ add? ? executed : executed - commission_amount
296
+ end
297
+
298
+ def sale_name
299
+ sale.name
300
+ end
301
+
302
+ def base_currency
303
+ sale.currency_id
304
+ end
305
+
306
+ def quote_currency
307
+ sale_pair.quote_currency_id
308
+ end
309
+
310
+ def ratio
311
+ sale_ratio = sale.ratio < 1 ? 1 : sale.ratio
312
+ (1 / sale_ratio).to_d.round(Launchpad::IEO::RATIO_PRECISION, BigDecimal::ROUND_DOWN)
313
+ end
314
+
315
+ def calculate_share
316
+ self.executed = contribution * ratio
317
+ self.refunded = contribution - executed
318
+
319
+ self.commission_amount = executed * commission_rate
320
+
321
+ tokens_ordered = (executed_without_commission / sale_pair.price).round(Launchpad::IEO::TOKENS_AMOUNT_PRECISION, BigDecimal::ROUND_DOWN)
322
+ self.tokens_locked = tokens_ordered * sale.lockup_percentage.to_d
323
+ self.tokens_received = tokens_ordered * (1 - sale.lockup_percentage.to_d)
324
+ end
325
+
326
+ def calculate_refund
327
+ # This logic may become complex later.
328
+ self.refunded = calculate_contribution
329
+ end
330
+
331
+ def calculate_refunded
332
+ self.refunded = contribution - executed
333
+ self.refunded = calculate_execute_refunded
334
+ end
335
+
336
+ def add?
337
+ sale.fees_policy.add?
338
+ end
339
+
340
+ def calculate_contribution
341
+ add? ? contribution + contribution * commission_rate : contribution
342
+ end
343
+
344
+ def calculate_release
345
+ self.tokens_received += tokens_locked
346
+ self.tokens_locked = 0
347
+ end
348
+
349
+ def lock_transfer_params
350
+ type = fetch_currency(sale_pair.quote_currency_id)
351
+ .fetch(:type)
352
+ .to_sym
353
+
354
+ operations =
355
+ [
356
+ {
357
+ currency: sale_pair.quote_currency_id,
358
+ amount: calculate_contribution,
359
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[type][:main], uid: uid},
360
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[type][:locked], uid: uid}
361
+ }
362
+ ].delete_if { |op| op[:amount].zero? }
363
+
364
+ {
365
+ key: "IEO-lock-#{id}",
366
+ category: "purchases",
367
+ description: "Lock funds for IEO order #{id}",
368
+ operations: operations
369
+ }
370
+ end
371
+
372
+ def execute_transfer_params
373
+ base_currency_type = fetch_currency(sale.currency_id)
374
+ .fetch(:type)
375
+ .to_sym
376
+ quote_currency_type = fetch_currency(sale_pair.quote_currency_id)
377
+ .fetch(:type)
378
+ .to_sym
379
+
380
+ operations =
381
+ [
382
+ { # Debit seller main liabilities & credit user locked liabilities with base currency.
383
+ currency: sale.currency_id,
384
+ amount: tokens_locked,
385
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:main],
386
+ uid: sale.owner_uid},
387
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:locked],
388
+ uid: uid}
389
+ },
390
+ { # Debit seller main liabilities & credit user main liabilities with base currency.
391
+ currency: sale.currency_id,
392
+ amount: tokens_received,
393
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:main],
394
+ uid: sale.owner_uid},
395
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:main],
396
+ uid: uid}
397
+ },
398
+ { # Debit user locked liabilities & credit seller main liabilities with quote currency.
399
+ currency: sale_pair.quote_currency_id,
400
+ amount: executed_without_commission,
401
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:locked],
402
+ uid: uid},
403
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:main],
404
+ uid: sale.owner_uid}
405
+ },
406
+ { # Debit user locked liabilities & credit user main liabilities with quote currency.
407
+ currency: sale_pair.quote_currency_id,
408
+ amount: calculate_execute_refunded,
409
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:locked],
410
+ uid: uid},
411
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:main],
412
+ uid: uid}
413
+ },
414
+ { # Debit user locked liabilities & credit main revenues with quote currency.
415
+ currency: sale_pair.quote_currency_id,
416
+ amount: commission_amount,
417
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:locked],
418
+ uid: uid},
419
+ account_dst: {code: Launchpad::IEO::REVENUE_CODES[quote_currency_type][:main]}
420
+ }
421
+ ].delete_if { |op| op[:amount].zero? }
422
+
423
+ {
424
+ key: "IEO-execute-#{id}",
425
+ category: :purchases,
426
+ description: "Execute IEO order #{id}",
427
+ operations: operations
428
+ }
429
+ end
430
+
431
+ def calculate_execute_refunded
432
+ add? ? refunded + refunded * commission_rate : refunded
433
+ end
434
+
435
+ def release_transfer_params
436
+ base_currency_type = fetch_currency(sale.currency_id)
437
+ .fetch(:type)
438
+ .to_sym
439
+
440
+ operations =
441
+ [
442
+ { # Debit user locked liabilities & credit user main liabilities with base currency.
443
+ currency: sale.currency_id,
444
+ amount: tokens_locked,
445
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:locked],
446
+ uid: uid},
447
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:main],
448
+ uid: uid}
449
+ }
450
+ ].delete_if { |op| op[:amount].zero? }
451
+
452
+ {
453
+ key: "IEO-release-#{id}",
454
+ category: :purchases,
455
+ description: "Release IEO order #{id}",
456
+ operations: operations
457
+ }
458
+ end
459
+
460
+ def cancel_transfer_params
461
+ quote_currency_type = fetch_currency(sale_pair.quote_currency_id)
462
+ .fetch(:type)
463
+ .to_sym
464
+
465
+ operations =
466
+ [
467
+ { # Debit user locked liabilities & credit user main liabilities with quote currency.
468
+ currency: sale_pair.quote_currency_id,
469
+ amount: refunded,
470
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:locked],
471
+ uid: uid},
472
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:main],
473
+ uid: uid}
474
+ }
475
+ ]
476
+ if add?
477
+ # Debit user locked liabilities & credit main revenues with quote currency.
478
+ operations << {
479
+ currency: sale_pair.quote_currency_id,
480
+ amount: commission_amount,
481
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[quote_currency_type][:locked],
482
+ uid: uid},
483
+ account_dst: {code: Launchpad::IEO::REVENUE_CODES[quote_currency_type][:main]}
484
+ }
485
+ end
486
+
487
+ operations.delete_if { |op| op[:amount].zero? }
488
+
489
+ {
490
+ key: "IEO-cancel-#{id}",
491
+ category: :purchases,
492
+ description: "Cancel IEO order #{id}",
493
+ operations: operations
494
+ }
495
+ end
496
+
497
+ def release_funds_params(funds)
498
+ base_currency_type = fetch_currency(sale.currency_id)
499
+ .fetch(:type)
500
+ .to_sym
501
+ operations =
502
+ [
503
+ { # Debit user locked liabilities & credit user main liabilities with base currency.
504
+ currency: sale.currency_id,
505
+ amount: funds,
506
+ account_src: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:locked],
507
+ uid: uid},
508
+ account_dst: {code: Launchpad::IEO::LIABILITY_CODES[base_currency_type][:main],
509
+ uid: uid}
510
+ }
511
+ ].delete_if { |op| op[:amount].zero? }
512
+
513
+ {
514
+ key: generate_key,
515
+ category: :purchases,
516
+ description: "Release IEO order locked funds #{id}",
517
+ operations: operations
518
+ }
519
+ end
520
+
521
+ def generate_key
522
+ "IEO-fund-release-#{id}-#{transfer_keys.grep(/IEO-fund-release-/).size}"
523
+ end
524
+
525
+ def perform_transfer(transfer_params)
526
+ Peatio::ManagementAPIV2::Client.new.create_transfer(transfer_params)
527
+ transfer_keys << transfer_params[:key]
528
+ end
529
+
530
+ def enqueue_execute_job
531
+ Launchpad::IEO::OrderExecuteWorker.perform_async(to_sgid(for: "order_execute"))
532
+ end
533
+
534
+ def enqueue_released_job
535
+ Launchpad::IEO::OrderReleaseWorker.perform_async(to_sgid(for: "order_release"))
536
+ end
537
+
538
+ def enqueue_refund_job
539
+ Launchpad::IEO::OrderRefundWorker.perform_async(to_sgid(for: "order_refund"))
540
+ end
541
+
542
+ private
543
+
544
+ # TODO: Move this method to helper or somewhere else.
545
+ def fetch_currency(code)
546
+ Rails.cache.fetch("currency_#{code}", expires_in: 1.day) do
547
+ Peatio::ManagementAPIV2::Client.new.currency(code: code)
548
+ end
549
+ end
550
+ end
551
+ end
552
+ end
553
+
554
+ # == Schema Information
555
+ # Schema version: 20191001145525
556
+ #
557
+ # Table name: ieo_orders
558
+ #
559
+ # id :bigint not null, primary key
560
+ # sale_pair_id :bigint not null
561
+ # uid :string(255) not null
562
+ # contribution :decimal(32, 16) not null
563
+ # executed :decimal(32, 16) default(0.0), not null
564
+ # refunded :decimal(32, 16) default(0.0), not null
565
+ # tokens_received :decimal(32, 16) default(0.0), not null
566
+ # commission_rate :decimal(16, 16) default(0.0), not null
567
+ # commission_amount :decimal(32, 16) default(0.0), not null
568
+ # transfer_keys :string(1000)
569
+ # state :string(32) not null
570
+ # created_at :datetime not null
571
+ # updated_at :datetime not null
572
+ #
573
+ # Indexes
574
+ #
575
+ # index_ieo_orders_on_sale_pair_id (sale_pair_id)
576
+ #