taunchpad 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|