bitsy-bitcoin 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +23 -0
  4. data/app/assets/javascripts/bitsy/application.js +13 -0
  5. data/app/assets/stylesheets/bitsy/application.css +15 -0
  6. data/app/controllers/bitsy/application_controller.rb +4 -0
  7. data/app/controllers/bitsy/v1/payment_depots_controller.rb +29 -0
  8. data/app/controllers/bitsy/v1/syncs_controller.rb +11 -0
  9. data/app/controllers/bitsy/v1/truncations_controller.rb +11 -0
  10. data/app/helpers/bitsy/application_helper.rb +4 -0
  11. data/app/jobs/bitsy/payment_job.rb +14 -0
  12. data/app/models/bitsy/payment_depot.rb +125 -0
  13. data/app/models/bitsy/payment_transaction.rb +51 -0
  14. data/app/serializers/bitsy/payment_depot_serializer.rb +16 -0
  15. data/app/services/bitsy/amount_for_initial_tax_calculator.rb +18 -0
  16. data/app/services/bitsy/associates_transactions.rb +13 -0
  17. data/app/services/bitsy/builds_send_many_hash.rb +35 -0
  18. data/app/services/bitsy/creates_transaction.rb +25 -0
  19. data/app/services/bitsy/forward_tax_calculator.rb +17 -0
  20. data/app/services/bitsy/forwards_payments.rb +31 -0
  21. data/app/services/bitsy/processes_payments.rb +20 -0
  22. data/app/services/bitsy/selects_transactions_for_sync.rb +13 -0
  23. data/app/services/bitsy/sends_payments.rb +14 -0
  24. data/app/services/bitsy/stockpiles_transaction.rb +24 -0
  25. data/app/services/bitsy/syncs_transaction.rb +26 -0
  26. data/app/services/bitsy/syncs_transactions.rb +17 -0
  27. data/app/services/bitsy/updates_transaction.rb +13 -0
  28. data/app/views/layouts/bitsy/application.html.erb +14 -0
  29. data/config/bitsy.yml +19 -0
  30. data/config/initializers/bitsy.rb +16 -0
  31. data/config/routes.rb +11 -0
  32. data/db/migrate/20140607015614_create_bitsy_payment_depots.rb +18 -0
  33. data/db/migrate/20140607015859_create_bitsy_payment_transactions.rb +17 -0
  34. data/lib/bitsy.rb +39 -0
  35. data/lib/bitsy/config.rb +28 -0
  36. data/lib/bitsy/engine.rb +8 -0
  37. data/lib/bitsy/version.rb +3 -0
  38. data/lib/clock.rb +10 -0
  39. data/lib/generators/bitsy/config/templates/bitsy.yml +19 -0
  40. data/lib/generators/bitsy/config/templates/initializer.rb +16 -0
  41. data/lib/generators/bitsy/config_generator.rb +33 -0
  42. data/lib/tasks/bitsy_tasks.rake +4 -0
  43. metadata +344 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 44f785dde6fd7ae7af5ce67976e67b1e353f8d55
4
+ data.tar.gz: 7fefa972daa9873c6c5c008903c05f8cc24f14dc
5
+ SHA512:
6
+ metadata.gz: fa0e5593e0c381e21eeeccb82a696d1bc24614310dcb49627e70d9ed06df471e21ec2a68538ad11874ea4b2ede49e963781c8126148f16a5422694ae09b36109
7
+ data.tar.gz: ceaf5517db9d04d0cd137cc28c69ec86dabb3de056cf5c23926a2ff2a39c5e431c7843503e50d9c28112166204407a66fd95f7bb106f1a4db15307b6879453c6
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
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 = 'Bitsy'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module Bitsy
2
+ class ApplicationController < ActionController::API
3
+ end
4
+ end
@@ -0,0 +1,29 @@
1
+ module Bitsy
2
+ module V1
3
+ class PaymentDepotsController < ApplicationController
4
+
5
+ def show
6
+ @payment_depot = PaymentDepot.find(params[:id])
7
+ render json: @payment_depot
8
+ end
9
+
10
+ def create
11
+ @payment_depot = PaymentDepot.create(payment_depot_params)
12
+ render json: @payment_depot
13
+ end
14
+
15
+ private
16
+
17
+ def payment_depot_params
18
+ params.require(:payment_depot).permit(
19
+ :min_payment,
20
+ :initial_tax_rate,
21
+ :added_tax_rate,
22
+ :owner_address,
23
+ :tax_address,
24
+ )
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ module Bitsy
2
+ module V1
3
+ class SyncsController < ApplicationController
4
+
5
+ def create
6
+ PaymentJob.new.perform
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Bitsy
2
+ module V1
3
+ class TruncationsController < ApplicationController
4
+
5
+ def create
6
+ DatabaseCleaner.clean_with :truncation
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Bitsy
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ module Bitsy
2
+ class PaymentJob
3
+
4
+ include Sidekiq::Worker
5
+
6
+ def perform
7
+ ProcessesPayments.for(
8
+ bit_wallet: Bitsy.bit_wallet,
9
+ bit_wallet_master_account: Bitsy.master_account
10
+ )
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,125 @@
1
+ require 'securerandom'
2
+
3
+ module Bitsy
4
+ class PaymentDepot < ActiveRecord::Base
5
+ has_many :transactions, class_name: 'PaymentTransaction'
6
+
7
+ alias_attribute :balance, :balance_cache
8
+ after_initialize :set_uuid
9
+ before_create :set_bitcoin_address
10
+ before_create :set_balance_cache
11
+ delegate :balance, to: :bit_wallet_account
12
+ scope :with_balance, -> { where('balance_cache > 0.0') }
13
+ validate(
14
+ :initial_tax_rate,
15
+ inclusion: {
16
+ in: [0.0..1.0],
17
+ message: 'must be a value within 0.0 and 1.0'
18
+ }
19
+ )
20
+ validate :address, uniqueness: true
21
+ validate :owner_address, presence: true
22
+ validate :tax_address, presence: true
23
+ validate :uuid, uniqueness: true, presence: true
24
+
25
+ delegate :bit_wallet, to: :Bitsy
26
+
27
+ def initial_owner_rate
28
+ self.min_payment * (1 - self.initial_tax_rate)
29
+ end
30
+
31
+ def balance_tax_amount
32
+ ForwardTaxCalculator.calculate(self.balance,
33
+ self.min_payment,
34
+ self.total_received_amount,
35
+ self.initial_tax_rate,
36
+ self.added_tax_rate)
37
+ end
38
+
39
+ def total_received_amount
40
+ self.transactions.credits.sum(:amount)
41
+ end
42
+
43
+ def total_tax_sent
44
+ tax_transactions.sum(:amount).abs
45
+ end
46
+
47
+ def total_owner_sent
48
+ owner_transactions.sum(:amount).abs
49
+ end
50
+
51
+ def min_payment_received?
52
+ self.total_received_amount >= self.min_payment
53
+ end
54
+
55
+ def initial_fee_received
56
+ if min_payment_received?
57
+ self.min_payment
58
+ else
59
+ self.total_received_amount
60
+ end
61
+ end
62
+
63
+ def added_fee_received
64
+ if min_payment_received?
65
+ self.total_received_amount - self.min_payment
66
+ else
67
+ 0.0
68
+ end
69
+ end
70
+
71
+ def initial_tax_amount
72
+ initial_fee_received * self.initial_tax_fee
73
+ end
74
+
75
+ def added_tax_amount
76
+ added_fee_received * self.added_tax_fee
77
+ end
78
+
79
+ def balance
80
+ self.balance_cache = bit_wallet_account_balance
81
+ end
82
+
83
+ def has_balance?
84
+ self.balance > 0
85
+ end
86
+
87
+ def bitcoin_account_name
88
+ self.uuid
89
+ end
90
+
91
+ private
92
+
93
+ def set_bitcoin_address
94
+ if self.address.blank?
95
+ self.address = bit_wallet_account.addresses.new.address
96
+ end
97
+ end
98
+
99
+ def set_balance_cache
100
+ self.balance_cache = bit_wallet_account.balance
101
+ end
102
+
103
+ def tax_transactions
104
+ self.transactions.debit.tax
105
+ end
106
+
107
+ def owner_transactions
108
+ self.transactions.debit.non_tax
109
+ end
110
+
111
+ def bit_wallet_account
112
+ return @bit_wallet_account if @bit_wallet_account
113
+ @bit_wallet_account = bit_wallet.accounts.new(self.bitcoin_account_name)
114
+ end
115
+
116
+ def bit_wallet_account_balance
117
+ bit_wallet_account.balance
118
+ end
119
+
120
+ def set_uuid
121
+ self.uuid ||= UUIDTools::UUID.random_create.to_s
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,51 @@
1
+ module Bitsy
2
+ class PaymentTransaction < ActiveRecord::Base
3
+
4
+ belongs_to :payment_depot
5
+
6
+ scope :safely_confirmed, -> {
7
+ column_name = "#{self.table_name}.confirmations"
8
+ where("#{column_name} >= #{Bitsy.config.safe_confirmation_threshold}")
9
+ }
10
+ scope :forwarded, -> {
11
+ where("#{self.table_name}.forwarding_transaction_id IS NOT NULL")
12
+ }
13
+ scope :not_forwarded, -> {
14
+ where("#{self.table_name}.forwarding_transaction_id IS NULL")
15
+ }
16
+ scope :received, -> { where(payment_type: 'receive') }
17
+ scope :received_by, lambda { |address| where(receiving_address: address) }
18
+ scope :sent_by, lambda { |address| where(sending_address: address) }
19
+ scope :tax, lambda { where(payment_type: 'tax') }
20
+ scope :non_tax, lambda { where('payment_type != ?', 'tax') }
21
+ scope :matching_bit_wallet_transaction, lambda { |bw_tx|
22
+ where(
23
+ transaction_id: bw_tx.id,
24
+ receiving_address: bw_tx.address_str,
25
+ amount: bw_tx.amount,
26
+ occurred_at: bw_tx.occurred_at,
27
+ received_at: bw_tx.received_at
28
+ )
29
+ }
30
+ scope :for_forwarding, -> { safely_confirmed.not_forwarded.received }
31
+ scope :credits, -> { received }
32
+
33
+ delegate :min_payment, to: :payment_depot, prefix: true
34
+ delegate :balance, to: :payment_depot, prefix: true
35
+ delegate :initial_tax_rate, to: :payment_depot, prefix: true
36
+ delegate :added_tax_rate, to: :payment_depot, prefix: true
37
+ delegate :total_received_amount, to: :payment_depot, prefix: true
38
+
39
+ def forward_tax_fee
40
+ ForwardTaxCalculator.calculate(self.amount,
41
+ self.payment_depot_min_payment,
42
+ self.payment_depot_total_received_amount,
43
+ self.payment_depot_initial_tax_rate,
44
+ self.payment_depot_added_tax_rate)
45
+ end
46
+
47
+ def owner_fee
48
+ self.amount - self.forward_tax_fee
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,16 @@
1
+ module Bitsy
2
+ class PaymentDepotSerializer < ActiveModel::Serializer
3
+
4
+ attributes(
5
+ :id,
6
+ :min_payment_received,
7
+ :total_received_amount,
8
+ :address,
9
+ )
10
+
11
+ def min_payment_received
12
+ object.min_payment_received?
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Bitsy
2
+ class AmountForInitialTaxCalculator
3
+
4
+ def self.calculate(payment, min_payment, total_received_amount)
5
+ if total_received_amount < payment
6
+ fail ArgumentError, "total amount recieved (#{total_received_amount}) cannot possibly be lower than the payment (#{payment}))"
7
+ elsif total_received_amount > min_payment
8
+ total_received_prior_to_payment = total_received_amount - payment
9
+ amount = min_payment - total_received_prior_to_payment
10
+ return 0 if amount < 0
11
+ amount
12
+ else
13
+ payment
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Bitsy
2
+ class AssociatesTransactions
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ payment_txs = ctx.fetch(:payment_transactions)
8
+ forwarding_transaction_id = ctx.fetch(:forwarding_transaction_id)
9
+ payment_txs.update_all(forwarding_transaction_id: forwarding_transaction_id)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ module Bitsy
2
+ class BuildsSendManyHash
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ payment_txs = ctx.fetch(:payment_transactions)
8
+ send_many_hash = {}
9
+
10
+ payment_txs.each do |payment_tx|
11
+ tx_hash = for_transaction(payment_tx)
12
+ tx_hash.each do |address, value|
13
+ if send_many_hash.has_key?(address)
14
+ send_many_hash[address] = send_many_hash[address] + value
15
+ else
16
+ send_many_hash[address] = value
17
+ end
18
+ end
19
+ end
20
+
21
+ ctx[:send_many_hash] = send_many_hash
22
+ end
23
+
24
+ def self.for_transaction(payment_transaction)
25
+ payment_depot = payment_transaction.payment_depot
26
+ owner_address = payment_depot.owner_address
27
+ tax_address = payment_depot.tax_address
28
+ {
29
+ tax_address => payment_transaction.forward_tax_fee.to_f,
30
+ owner_address => payment_transaction.owner_fee.to_f
31
+ }
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module Bitsy
2
+ class CreatesTransaction
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ bit_wallet_tx = ctx.fetch(:bit_wallet_transaction)
8
+ payment_depot = PaymentDepot.find_by_address(bit_wallet_tx.address_str)
9
+ if payment_depot
10
+ payment_tx = PaymentTransaction.create(
11
+ payment_depot_id: payment_depot.id,
12
+ amount: bit_wallet_tx.amount,
13
+ receiving_address: bit_wallet_tx.address_str,
14
+ transaction_id: bit_wallet_tx.id,
15
+ confirmations: bit_wallet_tx.confirmations,
16
+ payment_type: bit_wallet_tx.category,
17
+ occurred_at: bit_wallet_tx.occurred_at,
18
+ received_at: bit_wallet_tx.received_at
19
+ )
20
+ ctx[:payment_transaction] = payment_tx
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Bitsy
2
+ class ForwardTaxCalculator
3
+
4
+ def self.calculate(payment, min_payment, total_received_amount, initial_tax_rate, added_tax_rate)
5
+ initial_tax_amount = AmountForInitialTaxCalculator.calculate(
6
+ payment,
7
+ min_payment,
8
+ total_received_amount,
9
+ )
10
+ initial_tax_fee = initial_tax_amount * initial_tax_rate
11
+ amount_for_added_tax = payment - initial_tax_amount
12
+ added_tax_fee = amount_for_added_tax * added_tax_rate
13
+ initial_tax_fee + added_tax_fee
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ module Bitsy
2
+ class ForwardsPayments
3
+
4
+ include LightService::Organizer
5
+ include LightService::Action
6
+
7
+ executed do |ctx|
8
+ payment_transactions = PaymentTransaction.for_forwarding
9
+
10
+ if past_threshold?(payment_transactions)
11
+ bit_wallet_master_account = ctx.fetch(:bit_wallet_master_account)
12
+ context = {
13
+ bit_wallet_master_account: bit_wallet_master_account,
14
+ payment_transactions: payment_transactions
15
+ }
16
+ with(context).reduce([
17
+ BuildsSendManyHash,
18
+ SendsPayments,
19
+ AssociatesTransactions
20
+ ])
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def self.past_threshold?(payment_txs)
27
+ payment_txs.sum(:amount) >= Bitsy.config.forward_threshold
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ module Bitsy
2
+ class ProcessesPayments
3
+
4
+ include LightService::Organizer
5
+
6
+ def self.for(args={})
7
+ bit_wallet = args.fetch(:bit_wallet)
8
+ bit_wallet_master_account = args.fetch(:bit_wallet_master_account)
9
+
10
+ ctx = { bit_wallet: bit_wallet,
11
+ bit_wallet_master_account: bit_wallet_master_account }
12
+ with(ctx).reduce([
13
+ SelectsTransactionsForSync,
14
+ SyncsTransactions,
15
+ ForwardsPayments
16
+ ])
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module Bitsy
2
+ class SelectsTransactionsForSync
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ bit_wallet = ctx.fetch(:bit_wallet)
8
+ txs = bit_wallet.recent_transactions.select {|tx| tx.category == "receive"}
9
+ ctx[:bit_wallet_transactions] = txs
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Bitsy
2
+ class SendsPayments
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ bit_wallet_master_account = ctx.fetch(:bit_wallet_master_account)
8
+ send_many_hash = ctx.fetch(:send_many_hash)
9
+ tx_id = bit_wallet_master_account.send_many(send_many_hash)
10
+ ctx[:forwarding_transaction_id] = tx_id
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ module Bitsy
2
+ class StockpilesTransaction
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ payment_tx = ctx.fetch(:payment_transaction)
8
+
9
+ if payment_tx && payment_tx.payment_type == "receive"
10
+ bit_wallet_tx = ctx.fetch(:bit_wallet_transaction)
11
+ bit_wallet_master_account = ctx.fetch(:bit_wallet_master_account)
12
+ bit_wallet = bit_wallet_tx.account.wallet
13
+ payment_depot = payment_tx.payment_depot
14
+
15
+ bit_wallet.move(
16
+ payment_depot.bitcoin_account_name,
17
+ bit_wallet_master_account.name,
18
+ payment_tx.amount.to_f
19
+ )
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Bitsy
2
+ class SyncsTransaction
3
+
4
+ include LightService::Organizer
5
+
6
+ def self.for(args={})
7
+ bit_wallet_tx = args.fetch(:bit_wallet_transaction)
8
+
9
+ payment_tx = PaymentTransaction.
10
+ matching_bit_wallet_transaction(bit_wallet_tx).first
11
+ ctx = { payment_transaction: payment_tx,
12
+ bit_wallet_transaction: bit_wallet_tx }
13
+
14
+ actions = []
15
+ if payment_tx
16
+ actions << UpdatesTransaction
17
+ else
18
+ ctx[:bit_wallet_master_account] = args.fetch(:bit_wallet_master_account)
19
+ actions += [CreatesTransaction, StockpilesTransaction]
20
+ end
21
+
22
+ with(ctx).reduce(actions)
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Bitsy
2
+ class SyncsTransactions
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ bit_wallet_txs = ctx.fetch(:bit_wallet_transactions)
8
+ bit_wallet_master_account = ctx.fetch(:bit_wallet_master_account)
9
+
10
+ bit_wallet_txs.each do |bit_wallet_tx|
11
+ SyncsTransaction.for(bit_wallet_transaction: bit_wallet_tx,
12
+ bit_wallet_master_account: bit_wallet_master_account)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Bitsy
2
+ class UpdatesTransaction
3
+
4
+ include LightService::Action
5
+
6
+ executed do |ctx|
7
+ bit_wallet_tx = ctx.fetch(:bit_wallet_transaction)
8
+ payment_tx = ctx.fetch(:payment_transaction)
9
+ payment_tx.update_attributes(confirmations: bit_wallet_tx.confirmations)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Bitsy</title>
5
+ <%= stylesheet_link_tag "bitsy/application", media: "all" %>
6
+ <%= javascript_include_tag "bitsy/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/bitsy.yml ADDED
@@ -0,0 +1,19 @@
1
+ defaults: &defaults
2
+ bitcoind:
3
+ host: "localhost"
4
+ username: "admin1"
5
+ password: "123"
6
+ port: "19001"
7
+ ssl: false
8
+
9
+ development:
10
+ <<: *defaults
11
+
12
+ test:
13
+ <<: *defaults
14
+
15
+ staging:
16
+ <<: *defaults
17
+
18
+ production:
19
+ <<: *defaults
@@ -0,0 +1,16 @@
1
+ Bitsy.load_config Rails.root.join("config", "bitsy.yml")
2
+
3
+ Bitsy.configure do |c|
4
+ # How much of a transaction fee to pay to the network
5
+ c.transaction_fee = 0.0001
6
+
7
+ # How much Bitcoin to accumulate before forwarding the money out
8
+ c.transaction_fee_threshold_multiplier = 200
9
+
10
+ # How many confirmations are needed to consider a transaction as complete
11
+ c.safe_confirmation_threshold = 0
12
+
13
+ # The name where all money is pooled before sending out. You will likely not
14
+ # need to change this value.
15
+ c.master_account_name = ""
16
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ Bitsy::Engine.routes.draw do
2
+
3
+ namespace :v1 do
4
+ resources :payment_depots
5
+ unless Rails.env.production?
6
+ resources :syncs
7
+ resources :truncations
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,18 @@
1
+ class CreateBitsyPaymentDepots < ActiveRecord::Migration
2
+ def change
3
+ create_table :bitsy_payment_depots do |t|
4
+ t.decimal :min_payment, null: false
5
+ t.decimal :initial_tax_rate, null: false
6
+ t.decimal :added_tax_rate, null: false
7
+ t.decimal :balance_cache, null: false
8
+ t.string :owner_address
9
+ t.string :address, null: false
10
+ t.datetime :created_at, null: false
11
+ t.datetime :updated_at, null: false
12
+ t.string :tax_address
13
+ t.string :uuid, null: false
14
+ end
15
+
16
+ add_index :bitsy_payment_depots, :uuid
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ class CreateBitsyPaymentTransactions < ActiveRecord::Migration
2
+ def change
3
+ create_table :bitsy_payment_transactions do |t|
4
+ t.references :payment_depot, null: false
5
+ t.decimal :amount, null: false
6
+ t.string :receiving_address, null: false
7
+ t.string :payment_type, null: false
8
+ t.integer :confirmations, null: false
9
+ t.string :transaction_id, null: false
10
+ t.string :forwarding_transaction_id
11
+ t.datetime :occurred_at, null: false
12
+ t.datetime :received_at, null: false
13
+ t.integer :confirmations, null: false, default: 0
14
+ t.timestamps
15
+ end
16
+ end
17
+ end
data/lib/bitsy.rb ADDED
@@ -0,0 +1,39 @@
1
+ require "rails-api"
2
+ require "sidekiq"
3
+ require "active_model_serializers"
4
+ require "bit_wallet"
5
+ require "clockwork"
6
+ require "daemons"
7
+ require "light-service"
8
+ require "uuidtools"
9
+ require "bitsy/engine"
10
+ require "bitsy/config"
11
+
12
+ module Bitsy
13
+ require "bitsy/engine" if defined?(Rails)
14
+
15
+ mattr_accessor :config
16
+
17
+ def self.load_config(path)
18
+ self.config ||= Config.new(path)
19
+ end
20
+
21
+ def self.configure
22
+ yield self.config
23
+ end
24
+
25
+ def self.master_account
26
+ bit_wallet.accounts.new(self.config.master_account_name)
27
+ end
28
+
29
+ def self.bit_wallet
30
+ BitWallet.at(
31
+ host: Bitsy.config.bitcoind.fetch(:host),
32
+ port: Bitsy.config.bitcoind.fetch(:port),
33
+ username: Bitsy.config.bitcoind.fetch(:username),
34
+ password: Bitsy.config.bitcoind.fetch(:password),
35
+ ssl: Bitsy.config.bitcoind.fetch(:ssl),
36
+ )
37
+ end
38
+
39
+ end
@@ -0,0 +1,28 @@
1
+ module Bitsy
2
+ class Config
3
+
4
+ attr_accessor(
5
+ :transaction_fee,
6
+ :transaction_fee_threshold_multiplier,
7
+ :safe_confirmation_threshold,
8
+ :master_account_name,
9
+ )
10
+
11
+ def initialize(file_path)
12
+ @config ||= YAML.load_file(file_path).
13
+ with_indifferent_access.
14
+ fetch(Rails.env.to_s)
15
+
16
+ @config.each do |k, v|
17
+ self.class.send :define_method, k do
18
+ @config.fetch(k)
19
+ end
20
+ end
21
+ end
22
+
23
+ def forward_threshold
24
+ transaction_fee * transaction_fee_threshold_multiplier
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ module Bitsy
2
+ class Engine < Rails::Engine
3
+
4
+ isolate_namespace Bitsy
5
+ engine_name "bitsy"
6
+
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Bitsy
2
+ VERSION = "0.0.1"
3
+ end
data/lib/clock.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'clockwork'
2
+ require "sidekiq"
3
+
4
+ module Clockwork
5
+ handler do |job|
6
+ Sidekiq::Client.push('class' => job, 'args' => [])
7
+ end
8
+
9
+ every(5.seconds, 'Bitsy::PaymentJob')
10
+ end
@@ -0,0 +1,19 @@
1
+ defaults: &defaults
2
+ bitcoind:
3
+ host: "localhost"
4
+ username: "admin1"
5
+ password: "123"
6
+ port: "19001"
7
+ ssl: false
8
+
9
+ development:
10
+ <<: *defaults
11
+
12
+ test:
13
+ <<: *defaults
14
+
15
+ staging:
16
+ <<: *defaults
17
+
18
+ production:
19
+ <<: *defaults
@@ -0,0 +1,16 @@
1
+ Bitsy.load_config Rails.root.join("config", "bitsy.yml")
2
+
3
+ Bitsy.configure do |c|
4
+ # How much of a transaction fee to pay to the network
5
+ c.transaction_fee = 0.0001
6
+
7
+ # How much Bitcoin to accumulate before forwarding the money out
8
+ c.transaction_fee_threshold_multiplier = 200
9
+
10
+ # How many confirmations are needed to consider a transaction as complete
11
+ c.safe_confirmation_threshold = 0
12
+
13
+ # The name where all money is pooled before sending out. You will likely not
14
+ # need to change this value.
15
+ c.master_account_name = ""
16
+ end
@@ -0,0 +1,33 @@
1
+ require "bitsy/engine"
2
+
3
+ module Bitsy
4
+ class ConfigGenerator < Rails::Generators::Base
5
+
6
+ desc "Creates a Bitsy config file in config/bitsy.yml, and initializer that loads the file"
7
+ source_root File.expand_path("../config/templates", __FILE__)
8
+
9
+ def create_config_file
10
+ copy_file "bitsy.yml", File.join("config", "bitsy.yml")
11
+ end
12
+
13
+ def create_initializer_file
14
+ copy_file(
15
+ "initializer.rb",
16
+ File.join("config", "initializers", "bitsy.rb"),
17
+ )
18
+ end
19
+
20
+ def ignore_bitsy_yml
21
+ append_file ".gitignore", "config/bitsy.yml"
22
+ end
23
+
24
+ def mount_on_routes
25
+ inject_into_file(
26
+ "config/routes.rb",
27
+ %Q( mount Bitsy::Engine, at: "bitsy"\n),
28
+ before: /^end/
29
+ )
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :bitsy do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,344 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitsy-bitcoin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ramon Tayag
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails-api
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: sidekiq
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: active_model_serializers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bit_wallet
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.7.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.7.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: clockwork
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.7.2
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.7.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: daemons
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.1'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: light-service
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: 0.2.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '='
123
+ - !ruby/object:Gem::Version
124
+ version: 0.2.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: uuidtools
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.1'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sqlite3
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec-rails
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 2.14.2
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 2.14.2
167
+ - !ruby/object:Gem::Dependency
168
+ name: capybara
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: vcr
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '2.4'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '2.4'
195
+ - !ruby/object:Gem::Dependency
196
+ name: webmock
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: 1.8.0
202
+ - - "<"
203
+ - !ruby/object:Gem::Version
204
+ version: '1.16'
205
+ type: :development
206
+ prerelease: false
207
+ version_requirements: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - ">="
210
+ - !ruby/object:Gem::Version
211
+ version: 1.8.0
212
+ - - "<"
213
+ - !ruby/object:Gem::Version
214
+ version: '1.16'
215
+ - !ruby/object:Gem::Dependency
216
+ name: bitcoin_testnet
217
+ requirement: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - '='
220
+ - !ruby/object:Gem::Version
221
+ version: 0.5.1
222
+ type: :development
223
+ prerelease: false
224
+ version_requirements: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - '='
227
+ - !ruby/object:Gem::Version
228
+ version: 0.5.1
229
+ - !ruby/object:Gem::Dependency
230
+ name: factory_girl_rails
231
+ requirement: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - "~>"
234
+ - !ruby/object:Gem::Version
235
+ version: '4.2'
236
+ type: :development
237
+ prerelease: false
238
+ version_requirements: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - "~>"
241
+ - !ruby/object:Gem::Version
242
+ version: '4.2'
243
+ - !ruby/object:Gem::Dependency
244
+ name: database_cleaner
245
+ requirement: !ruby/object:Gem::Requirement
246
+ requirements:
247
+ - - ">="
248
+ - !ruby/object:Gem::Version
249
+ version: '0'
250
+ type: :development
251
+ prerelease: false
252
+ version_requirements: !ruby/object:Gem::Requirement
253
+ requirements:
254
+ - - ">="
255
+ - !ruby/object:Gem::Version
256
+ version: '0'
257
+ - !ruby/object:Gem::Dependency
258
+ name: standalone_migrations
259
+ requirement: !ruby/object:Gem::Requirement
260
+ requirements:
261
+ - - ">="
262
+ - !ruby/object:Gem::Version
263
+ version: '0'
264
+ type: :development
265
+ prerelease: false
266
+ version_requirements: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: '0'
271
+ description: A mountable Rails engine to create a payment server that can handle the
272
+ money in your Bitcoin ecosystem
273
+ email:
274
+ - ramon.tayag@gmail.com
275
+ executables: []
276
+ extensions: []
277
+ extra_rdoc_files: []
278
+ files:
279
+ - MIT-LICENSE
280
+ - Rakefile
281
+ - app/assets/javascripts/bitsy/application.js
282
+ - app/assets/stylesheets/bitsy/application.css
283
+ - app/controllers/bitsy/application_controller.rb
284
+ - app/controllers/bitsy/v1/payment_depots_controller.rb
285
+ - app/controllers/bitsy/v1/syncs_controller.rb
286
+ - app/controllers/bitsy/v1/truncations_controller.rb
287
+ - app/helpers/bitsy/application_helper.rb
288
+ - app/jobs/bitsy/payment_job.rb
289
+ - app/models/bitsy/payment_depot.rb
290
+ - app/models/bitsy/payment_transaction.rb
291
+ - app/serializers/bitsy/payment_depot_serializer.rb
292
+ - app/services/bitsy/amount_for_initial_tax_calculator.rb
293
+ - app/services/bitsy/associates_transactions.rb
294
+ - app/services/bitsy/builds_send_many_hash.rb
295
+ - app/services/bitsy/creates_transaction.rb
296
+ - app/services/bitsy/forward_tax_calculator.rb
297
+ - app/services/bitsy/forwards_payments.rb
298
+ - app/services/bitsy/processes_payments.rb
299
+ - app/services/bitsy/selects_transactions_for_sync.rb
300
+ - app/services/bitsy/sends_payments.rb
301
+ - app/services/bitsy/stockpiles_transaction.rb
302
+ - app/services/bitsy/syncs_transaction.rb
303
+ - app/services/bitsy/syncs_transactions.rb
304
+ - app/services/bitsy/updates_transaction.rb
305
+ - app/views/layouts/bitsy/application.html.erb
306
+ - config/bitsy.yml
307
+ - config/initializers/bitsy.rb
308
+ - config/routes.rb
309
+ - db/migrate/20140607015614_create_bitsy_payment_depots.rb
310
+ - db/migrate/20140607015859_create_bitsy_payment_transactions.rb
311
+ - lib/bitsy.rb
312
+ - lib/bitsy/config.rb
313
+ - lib/bitsy/engine.rb
314
+ - lib/bitsy/version.rb
315
+ - lib/clock.rb
316
+ - lib/generators/bitsy/config/templates/bitsy.yml
317
+ - lib/generators/bitsy/config/templates/initializer.rb
318
+ - lib/generators/bitsy/config_generator.rb
319
+ - lib/tasks/bitsy_tasks.rake
320
+ homepage: https://github.com/ramontayag/bitsy
321
+ licenses:
322
+ - MIT
323
+ metadata: {}
324
+ post_install_message:
325
+ rdoc_options: []
326
+ require_paths:
327
+ - lib
328
+ required_ruby_version: !ruby/object:Gem::Requirement
329
+ requirements:
330
+ - - ">="
331
+ - !ruby/object:Gem::Version
332
+ version: '0'
333
+ required_rubygems_version: !ruby/object:Gem::Requirement
334
+ requirements:
335
+ - - ">="
336
+ - !ruby/object:Gem::Version
337
+ version: '0'
338
+ requirements: []
339
+ rubyforge_project:
340
+ rubygems_version: 2.2.2
341
+ signing_key:
342
+ specification_version: 4
343
+ summary: A mountable Rails engine to create a Bitcoin payment server
344
+ test_files: []