bitsy-bitcoin 0.0.1

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 (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: []