glueby 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/Gemfile +1 -0
  4. data/README.md +111 -6
  5. data/glueby.gemspec +1 -1
  6. data/lib/generators/glueby/contract/reissuable_token_generator.rb +26 -0
  7. data/lib/generators/glueby/contract/templates/key_table.rb.erb +3 -3
  8. data/lib/generators/glueby/contract/templates/reissuable_token_table.rb.erb +10 -0
  9. data/lib/generators/glueby/contract/templates/system_information_table.rb.erb +2 -2
  10. data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +1 -1
  11. data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +2 -2
  12. data/lib/generators/glueby/contract/templates/wallet_table.rb.erb +2 -2
  13. data/lib/glueby.rb +25 -0
  14. data/lib/glueby/configuration.rb +62 -0
  15. data/lib/glueby/contract.rb +2 -2
  16. data/lib/glueby/contract/active_record.rb +1 -0
  17. data/lib/glueby/contract/active_record/reissuable_token.rb +26 -0
  18. data/lib/glueby/contract/fee_estimator.rb +38 -0
  19. data/lib/glueby/contract/payment.rb +4 -4
  20. data/lib/glueby/contract/timestamp.rb +6 -6
  21. data/lib/glueby/contract/token.rb +69 -22
  22. data/lib/glueby/contract/tx_builder.rb +22 -19
  23. data/lib/glueby/fee_provider.rb +73 -0
  24. data/lib/glueby/fee_provider/tasks.rb +136 -0
  25. data/lib/glueby/generator/migrate_generator.rb +1 -1
  26. data/lib/glueby/internal/wallet.rb +28 -4
  27. data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +18 -3
  28. data/lib/glueby/internal/wallet/active_record/wallet.rb +15 -5
  29. data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +15 -5
  30. data/lib/glueby/internal/wallet/errors.rb +3 -0
  31. data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +36 -11
  32. data/lib/glueby/version.rb +1 -1
  33. data/lib/glueby/wallet.rb +3 -2
  34. data/lib/tasks/glueby/contract/timestamp.rake +1 -1
  35. data/lib/tasks/glueby/fee_provider.rake +13 -0
  36. metadata +16 -9
  37. data/.travis.yml +0 -7
  38. data/lib/glueby/contract/fee_provider.rb +0 -21
@@ -0,0 +1,26 @@
1
+ module Glueby
2
+ module Contract
3
+ module AR
4
+ class ReissuableToken < ::ActiveRecord::Base
5
+
6
+ # Get the script_pubkey corresponding to the color_id in Tapyrus::Script format
7
+ # @param [String] color_id
8
+ # @return [Tapyrus::Script]
9
+ def self.script_pubkey(color_id)
10
+ script_pubkey = Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).pluck(:script_pubkey).first
11
+ if script_pubkey
12
+ Tapyrus::Script.parse_from_payload(script_pubkey.htb)
13
+ end
14
+ end
15
+
16
+ # Check if the color_id is already stored
17
+ # @param [String] color_id
18
+ # @return [Boolean]
19
+ def self.saved?(color_id)
20
+ Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).exists?
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ module Glueby
2
+ module Contract
3
+ module FeeEstimator
4
+ # @param [Tapyrus::Tx] tx - The target tx
5
+ # @return fee by tapyrus(not TPC).
6
+ def fee(tx)
7
+ return 0 if Glueby.configuration.fee_provider_bears?
8
+ estimate_fee(tx)
9
+ end
10
+
11
+ private
12
+
13
+ # @private
14
+ # @abstract Override in subclasses. This is would be implemented an actual estimation logic.
15
+ # @param [Tapyrus::Tx] tx - The target tx
16
+ # @return fee by tapyrus(not TPC).
17
+ def estimate_fee(tx)
18
+ raise NotImplementedError
19
+ end
20
+ end
21
+
22
+ class FixedFeeEstimator
23
+ include FeeEstimator
24
+
25
+ def initialize(fixed_fee: 10_000)
26
+ @fixed_fee = fixed_fee
27
+ end
28
+
29
+ private
30
+
31
+ # @private
32
+ # @return fee by tapyrus(not TPC).
33
+ def estimate_fee(_tx)
34
+ @fixed_fee
35
+ end
36
+ end
37
+ end
38
+ end
@@ -32,11 +32,11 @@ module Glueby
32
32
  extend Glueby::Contract::TxBuilder
33
33
 
34
34
  class << self
35
- def transfer(sender:, receiver_address:, amount:, fee_provider: FixedFeeProvider.new)
35
+ def transfer(sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
36
36
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
37
37
 
38
38
  tx = Tapyrus::Tx.new
39
- dummy_fee = fee_provider.fee(dummy_tx(tx))
39
+ dummy_fee = fee_estimator.fee(dummy_tx(tx))
40
40
 
41
41
  sum, outputs = sender.internal_wallet.collect_uncolored_outputs(dummy_fee + amount)
42
42
  fill_input(tx, outputs)
@@ -44,13 +44,13 @@ module Glueby
44
44
  receiver_script = Tapyrus::Script.parse_from_addr(receiver_address)
45
45
  tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_script)
46
46
 
47
- fee = fee_provider.fee(tx)
47
+ fee = fee_estimator.fee(tx)
48
48
 
49
49
  fill_change_tpc(tx, sender, sum - fee - amount)
50
50
 
51
51
  tx = sender.internal_wallet.sign_tx(tx)
52
52
 
53
- Glueby::Internal::RPC.client.sendrawtransaction(tx.to_hex)
53
+ sender.internal_wallet.broadcast(tx)
54
54
  end
55
55
  end
56
56
  end
@@ -14,11 +14,11 @@ module Glueby
14
14
  include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
15
15
  module_function
16
16
 
17
- def create_tx(wallet, prefix, data, fee_provider)
17
+ def create_tx(wallet, prefix, data, fee_estimator)
18
18
  tx = Tapyrus::Tx.new
19
19
  tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: create_script(prefix, data))
20
20
 
21
- fee = fee_provider.fee(dummy_tx(tx))
21
+ fee = fee_estimator.fee(dummy_tx(tx))
22
22
  sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
23
23
  fill_input(tx, outputs)
24
24
 
@@ -50,7 +50,7 @@ module Glueby
50
50
 
51
51
  # @param [String] content Data to be hashed and stored in blockchain.
52
52
  # @param [String] prefix prefix of op_return data
53
- # @param [Glueby::Contract::FeeProvider] fee_provider
53
+ # @param [Glueby::Contract::FeeEstimator] fee_estimator
54
54
  # @param [Symbol] digest type which select of:
55
55
  # - :sha256
56
56
  # - :double_sha256
@@ -60,13 +60,13 @@ module Glueby
60
60
  wallet:,
61
61
  content:,
62
62
  prefix: '',
63
- fee_provider: Glueby::Contract::FixedFeeProvider.new,
63
+ fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
64
64
  digest: :sha256
65
65
  )
66
66
  @wallet = wallet
67
67
  @content = content
68
68
  @prefix = prefix
69
- @fee_provider = fee_provider
69
+ @fee_estimator = fee_estimator
70
70
  @digest = digest
71
71
  end
72
72
 
@@ -77,7 +77,7 @@ module Glueby
77
77
  def save!
78
78
  raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
79
79
 
80
- @tx = create_tx(@wallet, @prefix, digest_content, @fee_provider)
80
+ @tx = create_tx(@wallet, @prefix, digest_content, @fee_estimator)
81
81
  @txid = @wallet.internal_wallet.broadcast(@tx)
82
82
  end
83
83
 
@@ -55,14 +55,14 @@ module Glueby
55
55
  # @param issuer [Glueby::Wallet]
56
56
  # @param token_type [TokenTypes]
57
57
  # @param amount [Integer]
58
- # @return [Token] token
58
+ # @return [Array<token, Array<tx>>] Tuple of tx array and token object
59
59
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
60
60
  # @raise [InvalidAmount] if amount is not positive integer.
61
61
  # @raise [UnspportedTokenType] if token is not supported.
62
62
  def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1)
63
63
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
64
64
 
65
- txs, color_id, script_pubkey = case token_type
65
+ txs, color_id = case token_type
66
66
  when Tapyrus::Color::TokenTypes::REISSUABLE
67
67
  issue_reissuable_token(issuer: issuer, amount: amount)
68
68
  when Tapyrus::Color::TokenTypes::NON_REISSUABLE
@@ -72,33 +72,44 @@ module Glueby
72
72
  else
73
73
  raise Glueby::Contract::Errors::UnsupportedTokenType
74
74
  end
75
- txs.each { |tx| issuer.internal_wallet.broadcast(tx) }
76
- new(color_id: color_id, script_pubkey: script_pubkey)
75
+
76
+ [new(color_id: color_id), txs]
77
77
  end
78
78
 
79
79
  private
80
80
 
81
81
  def issue_reissuable_token(issuer:, amount:)
82
- estimated_fee = FixedFeeProvider.new.fee(Tapyrus::Tx.new)
83
- funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee)
84
- tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
82
+ funding_tx = create_funding_tx(wallet: issuer)
85
83
  script_pubkey = funding_tx.outputs.first.script_pubkey
86
84
  color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
87
- [[funding_tx, tx], color_id, script_pubkey]
85
+
86
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
87
+ # Store the script_pubkey for reissue the token.
88
+ Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
89
+
90
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
91
+ tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
92
+ tx = issuer.internal_wallet.broadcast(tx)
93
+ [[funding_tx, tx], color_id]
94
+ end
88
95
  end
89
96
 
90
97
  def issue_non_reissuable_token(issuer:, amount:)
91
98
  tx = create_issue_tx_for_non_reissuable_token(issuer: issuer, amount: amount)
99
+ tx = issuer.internal_wallet.broadcast(tx)
100
+
92
101
  out_point = tx.inputs.first.out_point
93
102
  color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
94
- [[tx], color_id, nil]
103
+ [[tx], color_id]
95
104
  end
96
105
 
97
106
  def issue_nft_token(issuer:)
98
107
  tx = create_issue_tx_for_nft_token(issuer: issuer)
108
+ tx = issuer.internal_wallet.broadcast(tx)
109
+
99
110
  out_point = tx.inputs.first.out_point
100
111
  color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
101
- [[tx], color_id, nil]
112
+ [[tx], color_id]
102
113
  end
103
114
  end
104
115
 
@@ -108,6 +119,7 @@ module Glueby
108
119
  # A wallet can issue the token only when it is REISSUABLE token.
109
120
  # @param issuer [Glueby::Wallet]
110
121
  # @param amount [Integer]
122
+ # @return [Array<String, tx>] Tuple of color_id and tx object
111
123
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
112
124
  # @raise [InvalidAmount] if amount is not positive integer.
113
125
  # @raise [InvalidTokenType] if token is not reissuable.
@@ -115,12 +127,17 @@ module Glueby
115
127
  def reissue!(issuer:, amount:)
116
128
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
117
129
  raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
118
- raise Glueby::Contract::Errors::UnknownScriptPubkey unless @script_pubkey
119
130
 
120
- estimated_fee = FixedFeeProvider.new.fee(Tapyrus::Tx.new)
121
- funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee, script: @script_pubkey)
122
- tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
123
- [funding_tx, tx].each { |tx| issuer.internal_wallet.broadcast(tx) }
131
+ if validate_reissuer(wallet: issuer)
132
+ funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey)
133
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
134
+ tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
135
+ tx = issuer.internal_wallet.broadcast(tx)
136
+
137
+ [color_id, tx]
138
+ else
139
+ raise Glueby::Contract::Errors::UnknownScriptPubkey
140
+ end
124
141
  end
125
142
 
126
143
  # Send the token to other wallet
@@ -128,7 +145,7 @@ module Glueby
128
145
  # @param sender [Glueby::Wallet] wallet to send this token
129
146
  # @param receiver_address [String] address to receive this token
130
147
  # @param amount [Integer]
131
- # @return [Token] receiver token
148
+ # @return [Array<String, tx>] Tuple of color_id and tx object
132
149
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
133
150
  # @raise [InsufficientTokens] if wallet does not have enough token to send.
134
151
  # @raise [InvalidAmount] if amount is not positive integer.
@@ -137,6 +154,7 @@ module Glueby
137
154
 
138
155
  tx = create_transfer_tx(color_id: color_id, sender: sender, receiver_address: receiver_address, amount: amount)
139
156
  sender.internal_wallet.broadcast(tx)
157
+ [color_id, tx]
140
158
  end
141
159
 
142
160
  # Burn token
@@ -170,12 +188,19 @@ module Glueby
170
188
  color_id.type
171
189
  end
172
190
 
191
+ # Return the script_pubkey of the token from ActiveRecord
192
+ # @return [String] script_pubkey
193
+ def script_pubkey
194
+ @script_pubkey ||= Glueby::Contract::AR::ReissuableToken.script_pubkey(@color_id.to_hex)
195
+ end
196
+
173
197
  # Return serialized payload
174
198
  # @return [String] payload
175
199
  def to_payload
176
200
  payload = +''
177
201
  payload << @color_id.to_payload
178
- payload << @script_pubkey.to_payload if @script_pubkey
202
+ payload << @script_pubkey.to_payload if script_pubkey
203
+ payload
179
204
  end
180
205
 
181
206
  # Restore token from payload
@@ -184,14 +209,36 @@ module Glueby
184
209
  def self.parse_from_payload(payload)
185
210
  color_id, script_pubkey = payload.unpack('a33a*')
186
211
  color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(color_id)
187
- script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey) if script_pubkey
188
- new(color_id: color_id, script_pubkey: script_pubkey)
212
+ if color_id.type == Tapyrus::Color::TokenTypes::REISSUABLE
213
+ raise ArgumentError, 'script_pubkey should not be empty' if script_pubkey.empty?
214
+ script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey)
215
+ Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
216
+ end
217
+ new(color_id: color_id)
189
218
  end
190
219
 
191
- def initialize(color_id:, script_pubkey:nil)
220
+ # Generate Token Instance
221
+ # @param color_id [String]
222
+ def initialize(color_id:)
192
223
  @color_id = color_id
193
- @script_pubkey = script_pubkey
224
+ end
225
+
226
+ private
227
+
228
+ # Verify that wallet is the issuer of the reissuable token
229
+ # reutrn [Boolean]
230
+ def validate_reissuer(wallet:)
231
+ addresses = wallet.internal_wallet.get_addresses
232
+ addresses.each do |address|
233
+ decoded_address = Tapyrus.decode_base58_address(address)
234
+ pubkey_hash_from_address = decoded_address[0]
235
+ pubkey_hash_from_script = Tapyrus::Script.parse_from_payload(script_pubkey.chunks[2])
236
+ if pubkey_hash_from_address == pubkey_hash_from_script.to_s
237
+ return true
238
+ end
239
+ end
240
+ false
194
241
  end
195
242
  end
196
243
  end
197
- end
244
+ end
@@ -3,26 +3,29 @@
3
3
  module Glueby
4
4
  module Contract
5
5
  module TxBuilder
6
+ # The amount of output in funding tx for Reissuable token.
7
+ FUNDING_TX_AMOUNT = 10_000
8
+
6
9
  def receive_address(wallet:)
7
10
  wallet.receive_address
8
11
  end
9
12
 
10
13
  # Create new public key, and new transaction that sends TPC to it
11
- def create_funding_tx(wallet:, amount:, script: nil, fee_provider: FixedFeeProvider.new)
14
+ def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new)
12
15
  tx = Tapyrus::Tx.new
13
- fee = fee_provider.fee(dummy_tx(tx))
16
+ fee = fee_estimator.fee(dummy_tx(tx))
14
17
 
15
- sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + amount)
18
+ sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + FUNDING_TX_AMOUNT)
16
19
  fill_input(tx, outputs)
17
20
 
18
21
  receiver_script = script ? script : Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
19
- tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_script)
22
+ tx.outputs << Tapyrus::TxOut.new(value: FUNDING_TX_AMOUNT, script_pubkey: receiver_script)
20
23
 
21
- fill_change_tpc(tx, wallet, sum - fee - amount)
24
+ fill_change_tpc(tx, wallet, sum - fee - FUNDING_TX_AMOUNT)
22
25
  wallet.internal_wallet.sign_tx(tx)
23
26
  end
24
27
 
25
- def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_provider: FixedFeeProvider.new)
28
+ def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
26
29
  tx = Tapyrus::Tx.new
27
30
 
28
31
  out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
@@ -34,7 +37,7 @@ module Glueby
34
37
  receiver_colored_script = receiver_script.add_color(color_id)
35
38
  tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
36
39
 
37
- fee = fee_provider.fee(dummy_tx(tx))
40
+ fee = fee_estimator.fee(dummy_tx(tx))
38
41
  fill_change_tpc(tx, issuer, output.value - fee)
39
42
  prev_txs = [{
40
43
  txid: funding_tx.txid,
@@ -45,18 +48,18 @@ module Glueby
45
48
  issuer.internal_wallet.sign_tx(tx, prev_txs)
46
49
  end
47
50
 
48
- def create_issue_tx_for_non_reissuable_token(issuer:, amount:, fee_provider: FixedFeeProvider.new)
49
- create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, fee_provider: fee_provider)
51
+ def create_issue_tx_for_non_reissuable_token(issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
52
+ create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, fee_estimator: fee_estimator)
50
53
  end
51
54
 
52
- def create_issue_tx_for_nft_token(issuer:, fee_provider: FixedFeeProvider.new)
53
- create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_provider: fee_provider)
55
+ def create_issue_tx_for_nft_token(issuer:, fee_estimator: FixedFeeEstimator.new)
56
+ create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_estimator: fee_estimator)
54
57
  end
55
58
 
56
- def create_issue_tx_from_out_point(token_type:, issuer:, amount:, fee_provider: FixedFeeProvider.new)
59
+ def create_issue_tx_from_out_point(token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
57
60
  tx = Tapyrus::Tx.new
58
61
 
59
- fee = fee_provider.fee(dummy_issue_tx_from_out_point)
62
+ fee = fee_estimator.fee(dummy_issue_tx_from_out_point)
60
63
  sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee)
61
64
  fill_input(tx, outputs)
62
65
 
@@ -78,7 +81,7 @@ module Glueby
78
81
  issuer.internal_wallet.sign_tx(tx)
79
82
  end
80
83
 
81
- def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_provider: FixedFeeProvider.new)
84
+ def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_estimator: FixedFeeEstimator.new)
82
85
  tx = Tapyrus::Tx.new
83
86
 
84
87
  out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
@@ -89,7 +92,7 @@ module Glueby
89
92
  receiver_colored_script = receiver_script.add_color(color_id)
90
93
  tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
91
94
 
92
- fee = fee_provider.fee(dummy_tx(tx))
95
+ fee = fee_estimator.fee(dummy_tx(tx))
93
96
  fill_change_tpc(tx, issuer, output.value - fee)
94
97
  prev_txs = [{
95
98
  txid: funding_tx.txid,
@@ -100,7 +103,7 @@ module Glueby
100
103
  issuer.internal_wallet.sign_tx(tx, prev_txs)
101
104
  end
102
105
 
103
- def create_transfer_tx(color_id:, sender:, receiver_address:, amount:, fee_provider: FixedFeeProvider.new)
106
+ def create_transfer_tx(color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
104
107
  tx = Tapyrus::Tx.new
105
108
 
106
109
  utxos = sender.internal_wallet.list_unspent
@@ -113,7 +116,7 @@ module Glueby
113
116
 
114
117
  fill_change_token(tx, sender, sum_token - amount, color_id)
115
118
 
116
- fee = fee_provider.fee(dummy_tx(tx))
119
+ fee = fee_estimator.fee(dummy_tx(tx))
117
120
  sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee)
118
121
  fill_input(tx, outputs)
119
122
 
@@ -121,7 +124,7 @@ module Glueby
121
124
  sender.internal_wallet.sign_tx(tx)
122
125
  end
123
126
 
124
- def create_burn_tx(color_id:, sender:, amount: 0, fee_provider: FixedFeeProvider.new)
127
+ def create_burn_tx(color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
125
128
  tx = Tapyrus::Tx.new
126
129
 
127
130
  utxos = sender.internal_wallet.list_unspent
@@ -130,7 +133,7 @@ module Glueby
130
133
 
131
134
  fill_change_token(tx, sender, sum_token - amount, color_id) if amount.positive?
132
135
 
133
- fee = fee_provider.fee(dummy_tx(tx))
136
+ fee = fee_estimator.fee(dummy_tx(tx))
134
137
 
135
138
  dust = 600 # in case that the wallet has output which has just fee amount.
136
139
  sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + dust)
@@ -0,0 +1,73 @@
1
+ module Glueby
2
+ class FeeProvider
3
+
4
+ autoload :Tasks, 'glueby/fee_provider/tasks'
5
+
6
+ class NoUtxosInUtxoPool < StandardError; end
7
+
8
+ WALLET_ID = 'FEE_PROVIDER_WALLET'
9
+ DEFAULT_FIXED_FEE = 1000
10
+ DEFAULT_UTXO_POOL_SIZE = 20
11
+
12
+ attr_reader :fixed_fee, :utxo_pool_size, :wallet,
13
+
14
+ class << self
15
+ attr_reader :config
16
+
17
+ # @param [Hash] config
18
+ # @option config [Integer] :fixed_fee The amount of fee which FeeProvider would provide to the txs
19
+ # @option opts [Integer] :utxo_pool_size The number of UTXOs in UTXO pool that is managed by FeeProvider
20
+ def configure(config)
21
+ @config = config
22
+ end
23
+
24
+ def provide(tx)
25
+ new.provide(tx)
26
+ end
27
+ end
28
+
29
+ def initialize
30
+ @wallet = begin
31
+ Internal::Wallet.load(WALLET_ID)
32
+ rescue Internal::Wallet::Errors::WalletNotFound => _
33
+ Internal::Wallet.create(WALLET_ID)
34
+ end
35
+
36
+ @fixed_fee = (FeeProvider.config && FeeProvider.config[:fixed_fee]) || DEFAULT_FIXED_FEE
37
+ @utxo_pool_size = (FeeProvider.config && FeeProvider.config[:utxo_pool_size]) || DEFAULT_UTXO_POOL_SIZE
38
+ end
39
+
40
+ # Provide an input for fee to the tx.
41
+ # @param [Tapyrus::Tx] tx - The tx that is provided fee as a input. It should be signed with ANYONECANPAY flag.
42
+ # @return [Tapyrus::Tx]
43
+ # @raise [ArgumentError] If the signatures that the tx inputs has don't have ANYONECANPAY flag.
44
+ # @raise [Glueby::FeeProvider::NoUtxosInUtxoPool] If there are no UTXOs for paying fee in FeeProvider's UTXO pool
45
+ def provide(tx)
46
+ tx.inputs.each do |txin|
47
+ sig = get_signature(txin.script_sig)
48
+ unless sig[-1].unpack1('C') & Tapyrus::SIGHASH_TYPE[:anyonecanpay] == Tapyrus::SIGHASH_TYPE[:anyonecanpay]
49
+ raise ArgumentError, 'All the signatures that the tx inputs has should have ANYONECANPAY flag.'
50
+ end
51
+ end
52
+
53
+ utxo = utxo_for_fee
54
+ out_point = Tapyrus::OutPoint.new(utxo[:txid].rhex, utxo[:vout])
55
+ tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
56
+
57
+ wallet.sign_tx(tx, for_fee_provider_input: true)
58
+ end
59
+
60
+ private
61
+
62
+ def utxo_for_fee
63
+ utxo = wallet.list_unspent.select { |o| !o[:color_id] && o[:amount] == fixed_fee }.sample
64
+ raise NoUtxosInUtxoPool, 'No UTXOs in Fee Provider UTXO pool. UTXOs should be created with "glueby:fee_provider:manage_utxo_pool" rake task' unless utxo
65
+ utxo
66
+ end
67
+
68
+ # Get Signature from P2PKH or CP2PKH script sig
69
+ def get_signature(script_sig)
70
+ script_sig.chunks.first.pushed_data
71
+ end
72
+ end
73
+ end