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.
- checksums.yaml +7 -0
- data/README.md +28 -0
- data/Rakefile +32 -0
- data/app/controllers/concerns/exception_handlers.rb +19 -0
- data/app/controllers/concerns/jwt_payload.rb +19 -0
- data/app/controllers/concerns/response.rb +25 -0
- data/app/controllers/launchpad/api/v2/admin/base_controller.rb +22 -0
- data/app/controllers/launchpad/api/v2/admin/ieo/orders_controller.rb +29 -0
- data/app/controllers/launchpad/api/v2/admin/ieo/sales_controller.rb +112 -0
- data/app/controllers/launchpad/api/v2/private/base_controller.rb +14 -0
- data/app/controllers/launchpad/api/v2/private/ieo/orders_controller.rb +97 -0
- data/app/controllers/launchpad/api/v2/private/ieo/sales_controller.rb +22 -0
- data/app/controllers/launchpad/api/v2/public/base_controller.rb +13 -0
- data/app/controllers/launchpad/api/v2/public/ieo/sales_controller.rb +56 -0
- data/app/controllers/launchpad/application_controller.rb +10 -0
- data/app/helpers/launchpad/application_helper.rb +4 -0
- data/app/models/launchpad/application_record.rb +5 -0
- data/app/models/launchpad/ieo.rb +21 -0
- data/app/models/launchpad/ieo/order.rb +576 -0
- data/app/models/launchpad/ieo/sale.rb +371 -0
- data/app/models/launchpad/ieo/sale_pair.rb +83 -0
- data/app/services/barong/management_api_v2/client.rb +33 -0
- data/app/services/management_api_v2/client.rb +73 -0
- data/app/services/management_api_v2/exception.rb +25 -0
- data/app/services/peatio/management_api_v2/client.rb +49 -0
- data/app/workers/launchpad/ieo/order_execute_worker.rb +26 -0
- data/app/workers/launchpad/ieo/order_refund_worker.rb +19 -0
- data/app/workers/launchpad/ieo/order_release_worker.rb +22 -0
- data/app/workers/launchpad/ieo/sale_cancel_worker.rb +20 -0
- data/app/workers/launchpad/ieo/sale_currency_list_worker.rb +19 -0
- data/app/workers/launchpad/ieo/sale_distribute_worker.rb +20 -0
- data/app/workers/launchpad/ieo/sale_finish_worker.rb +21 -0
- data/app/workers/launchpad/ieo/sale_pair_list_worker.rb +21 -0
- data/app/workers/launchpad/ieo/sale_release_funds_worker.rb +23 -0
- data/app/workers/launchpad/ieo/sale_start_worker.rb +21 -0
- data/config/initializers/active_model.rb +13 -0
- data/config/initializers/api_pagination.rb +33 -0
- data/config/initializers/inflections.rb +19 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20191120145404_create_launchpad_ieo.rb +52 -0
- data/db/migrate/20200814114105_add_fees_policy_in_sale.rb +5 -0
- data/lib/launchpad.rb +10 -0
- data/lib/launchpad/engine.rb +17 -0
- data/lib/launchpad/precision_validator.rb +25 -0
- data/lib/launchpad/version.rb +3 -0
- data/lib/tasks/launchpad_tasks.rake +4 -0
- metadata +229 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5d65be3730ac435f0d3c272bd0ba55e52ec0d80752a94507b2edbf82bb40ba11
|
4
|
+
data.tar.gz: 3e96820f0162d890421b8adda89e5109207b3a63455f0f02f0bc272d432a999f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f7966e8b4bf31b2353078a25a304fb6712a5efb055bb83b54462a8c548f806291881702963a448e91b8a71e17ead5813773f31008df3367fb07daa0cd96ab6b
|
7
|
+
data.tar.gz: 39394902fcbd300dbb3a59e9243ab7b0d078008217c2fb60639e9c75d525198f7591d6067be3ef9c7810341b953f456fe0762c24bad862b1599c1ff842de8593
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Launchpad
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'launchpad'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install launchpad
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Launchpad'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ExceptionHandlers
|
4
|
+
def self.included(base)
|
5
|
+
base.instance_eval do
|
6
|
+
|
7
|
+
rescue_from ActiveRecord::RecordInvalid do |e|
|
8
|
+
record = e.record
|
9
|
+
class_name = record.model_name.singular
|
10
|
+
errors = record.errors.api_messages.map { |err| "#{class_name}.#{err}" }
|
11
|
+
errors_response(errors)
|
12
|
+
end
|
13
|
+
|
14
|
+
rescue_from ActiveRecord::RecordNotFound do |_e|
|
15
|
+
error_response("record.not_found", 404)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWTPayload
|
4
|
+
def jwt_payload
|
5
|
+
@jwt_payload ||= request.env.fetch("jwt.payload", {}).symbolize_keys
|
6
|
+
end
|
7
|
+
|
8
|
+
def uid
|
9
|
+
jwt_payload[:uid]
|
10
|
+
end
|
11
|
+
|
12
|
+
def email
|
13
|
+
jwt_payload[:email]
|
14
|
+
end
|
15
|
+
|
16
|
+
def role
|
17
|
+
jwt_payload[:role]
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Response
|
4
|
+
def json_response(object, status=:ok)
|
5
|
+
render json: object, status: status
|
6
|
+
end
|
7
|
+
|
8
|
+
def error_response(error, status=422)
|
9
|
+
data = "#{controller_namespace}.#{error}"
|
10
|
+
json_response({errors: [data]}, status)
|
11
|
+
end
|
12
|
+
|
13
|
+
def errors_response(errors, status=422)
|
14
|
+
data = errors.map { |error| "#{controller_namespace}.#{error}" }
|
15
|
+
json_response({errors: data}, status)
|
16
|
+
end
|
17
|
+
|
18
|
+
def not_found
|
19
|
+
render plain: "404 Not Found", status: 404
|
20
|
+
end
|
21
|
+
|
22
|
+
def controller_namespace
|
23
|
+
self.class.module_parent.name.split("::").last.underscore
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Launchpad
|
5
|
+
module API
|
6
|
+
module V2
|
7
|
+
module Admin
|
8
|
+
class BaseController < ApplicationController
|
9
|
+
include JWTPayload
|
10
|
+
|
11
|
+
ADMIN_ROLES = %w[superadmin admin accountant compliance support technical].freeze
|
12
|
+
|
13
|
+
before_action :authorize_admin!
|
14
|
+
|
15
|
+
def authorize_admin!
|
16
|
+
not_found unless role.to_s.in?(ADMIN_ROLES)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Launchpad
|
4
|
+
module API
|
5
|
+
module V2
|
6
|
+
module Admin
|
7
|
+
module IEO
|
8
|
+
class OrdersController < BaseController
|
9
|
+
# GET /admin/orders
|
10
|
+
def index
|
11
|
+
ransack_params = {
|
12
|
+
uid_eq: params[:uid],
|
13
|
+
state_in: params[:state],
|
14
|
+
sale_id_eq: params[:sale_id],
|
15
|
+
sale_pair_id_eq: params[:sale_pair_id],
|
16
|
+
sale_pair_quote_currency_id_eq: params[:quote_unit],
|
17
|
+
sale_currency_id_eq: params[:base_unit]
|
18
|
+
}
|
19
|
+
|
20
|
+
orders = Launchpad::IEO::Order.ransack(ransack_params)
|
21
|
+
|
22
|
+
json_response(paginate(orders.result.includes(:sale, :sale_pair)), 200)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Launchpad
|
4
|
+
module API
|
5
|
+
module V2
|
6
|
+
module Admin
|
7
|
+
module IEO
|
8
|
+
class SalesController < BaseController
|
9
|
+
# GET /admin/sales
|
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.ransack(ransack_params)
|
18
|
+
json_response(paginate(sales.result), 200)
|
19
|
+
end
|
20
|
+
|
21
|
+
def show
|
22
|
+
json_response(Launchpad::IEO::Sale.find(params[:id]), 200)
|
23
|
+
end
|
24
|
+
|
25
|
+
# POST /admin/sales
|
26
|
+
def create
|
27
|
+
if sale_params[:pairs].blank?
|
28
|
+
error_response("sale_pair.missing_or_empty_pairs", 422)
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
sale = nil
|
33
|
+
ActiveRecord::Base.transaction do
|
34
|
+
sale = Launchpad::IEO::Sale.create!(sale_params.except(:pairs))
|
35
|
+
|
36
|
+
sale_params[:pairs].each do |pair|
|
37
|
+
sale.pairs.create!(pair)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
json_response(sale, 201)
|
42
|
+
end
|
43
|
+
|
44
|
+
# PUT /admin/sales/:id
|
45
|
+
def update
|
46
|
+
sale = Launchpad::IEO::Sale.find(params[:id])
|
47
|
+
states = Launchpad::IEO::Sale::EDITABLE_STATES
|
48
|
+
|
49
|
+
if sale.state.in?(states.keys.map(&:to_s))
|
50
|
+
sale.update!(sale_params.slice(*states[sale.state.to_sym]))
|
51
|
+
sale.send(:enqueue_distribute_job) if sale.ongoing? && sale_params["finishes_at"].present?
|
52
|
+
else
|
53
|
+
error_response("sale.non_editable", 422)
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
json_response(sale, 200)
|
58
|
+
end
|
59
|
+
|
60
|
+
def release_fund
|
61
|
+
sale = Launchpad::IEO::Sale.find(params[:id])
|
62
|
+
unless sale.finished?
|
63
|
+
error_response("launchpad_ieo_sale.state_mismatch", 422)
|
64
|
+
return
|
65
|
+
end
|
66
|
+
if sale_lockup_params[:release_fund].to_d.negative? || sale.lockup_percentage.to_d - sale_lockup_params[:release_fund].to_d < 0
|
67
|
+
error_response("launchpad_ieo_sale.invalid_lockup_percentage_or_negative", 422)
|
68
|
+
return
|
69
|
+
end
|
70
|
+
params.merge!(lockup_percentage: sale.lockup_percentage.to_d - sale_lockup_params[:release_fund].to_d)
|
71
|
+
sale.update!(sale_lockup_params.except(:release_fund))
|
72
|
+
sale.send(:enqueue_release_funds_job, sale_lockup_params[:release_fund].to_d)
|
73
|
+
json_response(sale, 200)
|
74
|
+
end
|
75
|
+
|
76
|
+
# PUT /admin/sales/:id/:event
|
77
|
+
def trigger_event
|
78
|
+
events = Launchpad::IEO::Sale.aasm.events.map { |e| e.name.to_s }
|
79
|
+
sale = Launchpad::IEO::Sale.find(params[:id])
|
80
|
+
|
81
|
+
begin
|
82
|
+
if params[:event].in?(events)
|
83
|
+
sale.send("#{params[:event]}!")
|
84
|
+
else
|
85
|
+
error_response("sale.event_not_found", 400)
|
86
|
+
return
|
87
|
+
end
|
88
|
+
rescue AASM::InvalidTransition
|
89
|
+
error_response("sale.transition_failed", 422)
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
json_response(sale, 200)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def sale_params
|
99
|
+
params.permit(:name, :description, :currency_id, :supply, :low_goal, :min_unit,
|
100
|
+
:commission, :owner_uid, :result, :lockup_percentage, :type, :min_amount,
|
101
|
+
:max_amount, :starts_at, :finishes_at, :fees_policy, pairs: %i[quote_currency_id price])
|
102
|
+
end
|
103
|
+
|
104
|
+
def sale_lockup_params
|
105
|
+
params.permit(:id, :lockup_percentage, :release_fund)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Launchpad
|
4
|
+
module API
|
5
|
+
module V2
|
6
|
+
module Private
|
7
|
+
module IEO
|
8
|
+
class OrdersController < BaseController
|
9
|
+
# POST /ieo/orders
|
10
|
+
# TODO: Improve handling of Management API exceptions.
|
11
|
+
def create
|
12
|
+
sale = Launchpad::IEO::Sale.public_states.find(order_params[:sale])
|
13
|
+
unless sale.ongoing?
|
14
|
+
error_response("order.sale_not_active", 422)
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
sale_pair = Launchpad::IEO::SalePair.find_by!(sale_id: sale.id, quote_currency_id: order_params[:quote_unit])
|
19
|
+
|
20
|
+
begin
|
21
|
+
response = Peatio::ManagementAPIV2::Client.new.balance(
|
22
|
+
uid: uid,
|
23
|
+
currency: order_params[:quote_unit]
|
24
|
+
)
|
25
|
+
rescue ManagementAPIV2::Exception
|
26
|
+
error_response("order.user_balance_checking_failed", 503)
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
if response[:balance].to_d < contribution(sale, order_params[:contribution].to_d)
|
31
|
+
error_response("order.insufficient_balance", 422)
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
order = Launchpad::IEO::Order.create!(
|
36
|
+
sale_pair_id: sale_pair.id,
|
37
|
+
uid: uid,
|
38
|
+
contribution: order_params[:contribution].to_d
|
39
|
+
)
|
40
|
+
|
41
|
+
begin
|
42
|
+
order.dispatch!
|
43
|
+
|
44
|
+
json_response(order, 201)
|
45
|
+
rescue ManagementAPIV2::Exception
|
46
|
+
order.reject!
|
47
|
+
error_response("order.transfers_creation_failed", 422)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# GET /ieo/orders
|
52
|
+
def index
|
53
|
+
ransack_params = {
|
54
|
+
uid_eq: uid,
|
55
|
+
state_in: params[:state],
|
56
|
+
sale_pair_sale_id_eq: params[:sale],
|
57
|
+
sale_pair_quote_currency_id_eq: params[:quote_unit],
|
58
|
+
sale_name_eq: params[:search]
|
59
|
+
}
|
60
|
+
ransack_params.merge!(created_at_gteq: Time.at(params[:time_from].to_i))\
|
61
|
+
unless params[:time_from].nil?
|
62
|
+
ransack_params.merge!(created_at_lteq: Time.at(params[:time_to].to_i))\
|
63
|
+
unless params[:time_to].nil?
|
64
|
+
|
65
|
+
orders = Launchpad::IEO::Order.ransack(ransack_params)
|
66
|
+
orders.sorts = "id desc"
|
67
|
+
json_response(paginate(orders.result.includes(:sale, :sale_pair)), 200)
|
68
|
+
end
|
69
|
+
|
70
|
+
# DELETE /ieo/orders/:id
|
71
|
+
def destroy
|
72
|
+
order = Launchpad::IEO::Order.find_by!(uid: uid, id: params[:id])
|
73
|
+
|
74
|
+
unless order.active?
|
75
|
+
error_response("order.non_active", 422)
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
order.cancel!
|
80
|
+
json_response(order, 200)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def contribution(sale, contrib)
|
86
|
+
sale.fees_policy.add? ? contrib + (contrib * sale.commission) : contrib
|
87
|
+
end
|
88
|
+
|
89
|
+
def order_params
|
90
|
+
params.permit(:sale, :quote_unit, :contribution)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Launchpad
|
4
|
+
module API
|
5
|
+
module V2
|
6
|
+
module Private
|
7
|
+
module IEO
|
8
|
+
class SalesController < BaseController
|
9
|
+
def show
|
10
|
+
launchpad = Launchpad::IEO::Sale.find(params[:id])
|
11
|
+
additional_params = {
|
12
|
+
generic_bids: launchpad.orders.where("state = ? OR state = ? OR state = ?", 'active', 'completed', 'purchased').last(50).as_json(uid: true),
|
13
|
+
my_bids: launchpad.orders.where(uid: uid).as_json(uid: true).last(50),
|
14
|
+
}
|
15
|
+
json_response(launchpad.as_json.merge(additional_params), 200)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|