glueby 0.3.0 → 0.4.0

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