glueby 0.12.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +3 -4
  3. data/README.md +3 -3
  4. data/glueby.gemspec +2 -1
  5. data/lib/generators/glueby/contract/templates/initializer.rb.erb +8 -8
  6. data/lib/generators/glueby/contract/templates/key_table.rb.erb +16 -16
  7. data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +2 -0
  8. data/lib/generators/glueby/contract/templates/token_metadata_table.rb.erb +13 -0
  9. data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +1 -0
  10. data/lib/generators/glueby/contract/{reissuable_token_generator.rb → token_generator.rb} +12 -1
  11. data/lib/glueby/block_syncer.rb +97 -97
  12. data/lib/glueby/configuration.rb +2 -0
  13. data/lib/glueby/contract/active_record/timestamp.rb +9 -2
  14. data/lib/glueby/contract/active_record/token_metadata.rb +8 -0
  15. data/lib/glueby/contract/active_record.rb +1 -0
  16. data/lib/glueby/contract/fee_estimator/auto.rb +9 -1
  17. data/lib/glueby/contract/fee_estimator.rb +12 -5
  18. data/lib/glueby/contract/timestamp/syncer.rb +1 -1
  19. data/lib/glueby/contract/timestamp/tx_builder/simple.rb +5 -1
  20. data/lib/glueby/contract/timestamp.rb +2 -2
  21. data/lib/glueby/contract/token.rb +161 -80
  22. data/lib/glueby/contract/tx_builder.rb +104 -67
  23. data/lib/glueby/fee_provider/tasks.rb +4 -1
  24. data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +26 -0
  25. data/lib/glueby/internal/wallet/active_record/key.rb +2 -2
  26. data/lib/glueby/internal/wallet/active_record/utxo.rb +9 -0
  27. data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +22 -9
  28. data/lib/glueby/internal/wallet/errors.rb +1 -0
  29. data/lib/glueby/internal/wallet/mysql_wallet_adapter.rb +17 -0
  30. data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +1 -1
  31. data/lib/glueby/internal/wallet.rb +16 -0
  32. data/lib/glueby/util/digest.rb +23 -0
  33. data/lib/glueby/utxo_provider/tasks.rb +3 -1
  34. data/lib/glueby/utxo_provider.rb +56 -9
  35. data/lib/glueby/version.rb +1 -1
  36. data/lib/tasks/glueby/block_syncer.rake +7 -0
  37. metadata +23 -5
@@ -43,6 +43,10 @@ module Glueby
43
43
  # token.amount(wallet: alice)
44
44
  # => 100
45
45
  #
46
+ # Issue with metadata / Get metadata
47
+ # token = Token.issue!(issuer: alice, amount: 100, metadata: 'metadata')
48
+ # token.metadata
49
+ # => "metadata"
46
50
  class Token
47
51
  include Glueby::Contract::TxBuilder
48
52
  extend Glueby::Contract::TxBuilder
@@ -57,22 +61,24 @@ module Glueby
57
61
  # @param token_type [TokenTypes]
58
62
  # @param amount [Integer]
59
63
  # @param split [Integer] The tx outputs should be split by specified number.
64
+ # @param fee_estimator [Glueby::Contract::FeeEstimator]
65
+ # @param metadata [String] The data to be stored in blockchain.
60
66
  # @return [Array<token, Array<tx>>] Tuple of tx array and token object
61
67
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
62
68
  # @raise [InvalidAmount] if amount is not positive integer.
63
69
  # @raise [InvalidSplit] if split is greater than 1 for NFT token.
64
- # @raise [UnspportedTokenType] if token is not supported.
65
- def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1, split: 1)
70
+ # @raise [UnsupportedTokenType] if token is not supported.
71
+ def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1, split: 1, fee_estimator: FeeEstimator::Fixed.new, metadata: nil)
66
72
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
67
73
  raise Glueby::Contract::Errors::InvalidSplit if token_type == Tapyrus::Color::TokenTypes::NFT && split > 1
68
74
 
69
75
  txs, color_id = case token_type
70
76
  when Tapyrus::Color::TokenTypes::REISSUABLE
71
- issue_reissuable_token(issuer: issuer, amount: amount, split: split)
77
+ issue_reissuable_token(issuer: issuer, amount: amount, split: split, fee_estimator: fee_estimator, metadata: metadata)
72
78
  when Tapyrus::Color::TokenTypes::NON_REISSUABLE
73
- issue_non_reissuable_token(issuer: issuer, amount: amount, split: split)
79
+ issue_non_reissuable_token(issuer: issuer, amount: amount, split: split, fee_estimator: fee_estimator, metadata: metadata)
74
80
  when Tapyrus::Color::TokenTypes::NFT
75
- issue_nft_token(issuer: issuer)
81
+ issue_nft_token(issuer: issuer, metadata: metadata)
76
82
  else
77
83
  raise Glueby::Contract::Errors::UnsupportedTokenType
78
84
  end
@@ -84,10 +90,35 @@ module Glueby
84
90
  Glueby::AR::SystemInformation.use_only_finalized_utxo?
85
91
  end
86
92
 
93
+ # Sign to pay-to-contract output.
94
+ #
95
+ # @param issuer [Glueby::Walelt] Issuer of the token
96
+ # @param tx [Tapyrus::Tx] The transaction to be signed with metadata
97
+ # @param funding_tx [Tapyrus::Tx] The funding transaction that has pay-to-contract output in its first output
98
+ # @param payment_base [String] The public key used to generate pay to contract public key
99
+ # @param metadata [String] Data that represents token metadata
100
+ # @return [Tapyrus::Tx] signed tx
101
+ def sign_to_p2c_output(issuer, tx, funding_tx, payment_base, metadata)
102
+ utxo = { txid: funding_tx.txid, vout: 0, script_pubkey: funding_tx.outputs[0].script_pubkey.to_hex }
103
+ issuer.internal_wallet.sign_to_pay_to_contract_address(tx, utxo, payment_base, metadata)
104
+ end
105
+
87
106
  private
88
107
 
89
- def issue_reissuable_token(issuer:, amount:, split: 1)
90
- funding_tx = create_funding_tx(wallet: issuer, only_finalized: only_finalized?)
108
+ def create_p2c_address(wallet, metadata)
109
+ p2c_address, payment_base = wallet.internal_wallet.create_pay_to_contract_address(metadata)
110
+ script = Tapyrus::Script.parse_from_addr(p2c_address)
111
+ [script, p2c_address, payment_base]
112
+ end
113
+
114
+ def issue_reissuable_token(issuer:, amount:, split: 1, fee_estimator:, metadata: nil)
115
+ script, p2c_address, payment_base = create_p2c_address(issuer, metadata) if metadata
116
+
117
+ # For reissuable token, we need funding transaction for every issuance.
118
+ # To make it easier for API users to understand whether a transaction is a new issue or a reissue,
119
+ # when Token.issue! is executed, a new address is created and tpc is sent to it to ensure that it is a new issue,
120
+ # and a transaction is created using that UTXO as input to create a new color_id.
121
+ funding_tx = create_funding_tx(wallet: issuer, script: script, only_finalized: only_finalized?)
91
122
  script_pubkey = funding_tx.outputs.first.script_pubkey
92
123
  color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
93
124
 
@@ -99,41 +130,82 @@ module Glueby
99
130
  # Store the script_pubkey for reissue the token.
100
131
  Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
101
132
 
102
- tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount, split: split)
133
+ tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount, split: split, fee_estimator: fee_estimator)
134
+ if metadata
135
+ Glueby::Contract::AR::TokenMetadata.create!(
136
+ color_id: color_id.to_hex,
137
+ metadata: metadata,
138
+ p2c_address: p2c_address,
139
+ payment_base: payment_base
140
+ )
141
+ tx = sign_to_p2c_output(issuer, tx, funding_tx, payment_base, metadata)
142
+ end
103
143
  tx = issuer.internal_wallet.broadcast(tx)
104
144
  [[funding_tx, tx], color_id]
105
145
  end
106
146
  end
107
147
 
108
- def issue_non_reissuable_token(issuer:, amount:, split: 1)
109
- funding_tx = create_funding_tx(wallet: issuer, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider?
110
- funding_tx = issuer.internal_wallet.broadcast(funding_tx) if funding_tx
148
+ def issue_non_reissuable_token(issuer:, amount:, split: 1, fee_estimator:, metadata: nil)
149
+ script, p2c_address, payment_base = create_p2c_address(issuer, metadata) if metadata
150
+ funding_tx = create_funding_tx(wallet: issuer, script: script, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider? || script
151
+ if funding_tx
152
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
153
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
154
+ end
155
+ end
111
156
 
112
- tx = create_issue_tx_for_non_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount, split: split)
113
- tx = issuer.internal_wallet.broadcast(tx)
157
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
158
+ tx = create_issue_tx_for_non_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount, split: split, fee_estimator: fee_estimator)
159
+ out_point = tx.inputs.first.out_point
160
+ color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
161
+ if metadata
162
+ Glueby::Contract::AR::TokenMetadata.create!(
163
+ color_id: color_id.to_hex,
164
+ metadata: metadata,
165
+ p2c_address: p2c_address,
166
+ payment_base: payment_base
167
+ )
168
+ tx = sign_to_p2c_output(issuer, tx, funding_tx, payment_base, metadata)
169
+ end
170
+ tx = issuer.internal_wallet.broadcast(tx)
114
171
 
115
- out_point = tx.inputs.first.out_point
116
- color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
117
- if funding_tx
118
- [[funding_tx, tx], color_id]
119
- else
120
- [[tx], color_id]
172
+ if funding_tx
173
+ [[funding_tx, tx], color_id]
174
+ else
175
+ [[tx], color_id]
176
+ end
121
177
  end
122
178
  end
123
179
 
124
- def issue_nft_token(issuer:)
125
- funding_tx = create_funding_tx(wallet: issuer, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider?
126
- funding_tx = issuer.internal_wallet.broadcast(funding_tx) if funding_tx
180
+ def issue_nft_token(issuer:, metadata: nil)
181
+ script, p2c_address, payment_base = create_p2c_address(issuer, metadata) if metadata
182
+ funding_tx = create_funding_tx(wallet: issuer, script: script, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider? || script
183
+ if funding_tx
184
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
185
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
186
+ end
187
+ end
127
188
 
128
- tx = create_issue_tx_for_nft_token(funding_tx: funding_tx, issuer: issuer)
129
- tx = issuer.internal_wallet.broadcast(tx)
189
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
190
+ tx = create_issue_tx_for_nft_token(funding_tx: funding_tx, issuer: issuer)
191
+ out_point = tx.inputs.first.out_point
192
+ color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
193
+ if metadata
194
+ Glueby::Contract::AR::TokenMetadata.create!(
195
+ color_id: color_id.to_hex,
196
+ metadata: metadata,
197
+ p2c_address: p2c_address,
198
+ payment_base: payment_base
199
+ )
200
+ tx = sign_to_p2c_output(issuer, tx, funding_tx, payment_base, metadata)
201
+ end
202
+ tx = issuer.internal_wallet.broadcast(tx)
130
203
 
131
- out_point = tx.inputs.first.out_point
132
- color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
133
- if funding_tx
134
- [[funding_tx, tx], color_id]
135
- else
136
- [[tx], color_id]
204
+ if funding_tx
205
+ [[funding_tx, tx], color_id]
206
+ else
207
+ [[tx], color_id]
208
+ end
137
209
  end
138
210
  end
139
211
  end
@@ -145,25 +217,28 @@ module Glueby
145
217
  # @param issuer [Glueby::Wallet]
146
218
  # @param amount [Integer]
147
219
  # @param split [Integer]
220
+ # @param fee_estimator [Glueby::Contract::FeeEstimator]
148
221
  # @return [Array<String, tx>] Tuple of color_id and tx object
149
222
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
150
223
  # @raise [InvalidAmount] if amount is not positive integer.
151
224
  # @raise [InvalidTokenType] if token is not reissuable.
152
225
  # @raise [UnknownScriptPubkey] when token is reissuable but it doesn't know script pubkey to issue token.
153
- def reissue!(issuer:, amount:, split: 1)
226
+ def reissue!(issuer:, amount:, split: 1, fee_estimator: FeeEstimator::Fixed.new)
154
227
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
155
228
  raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
156
229
 
157
- if validate_reissuer(wallet: issuer)
158
- funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey, only_finalized: only_finalized?)
159
- funding_tx = issuer.internal_wallet.broadcast(funding_tx)
160
- tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id, split: split)
161
- tx = issuer.internal_wallet.broadcast(tx)
230
+ token_metadata = Glueby::Contract::AR::TokenMetadata.find_by(color_id: color_id.to_hex)
231
+ raise Glueby::Contract::Errors::UnknownScriptPubkey unless valid_reissuer?(wallet: issuer, token_metadata: token_metadata)
162
232
 
163
- [color_id, tx]
164
- else
165
- raise Glueby::Contract::Errors::UnknownScriptPubkey
233
+ funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey, only_finalized: only_finalized?)
234
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
235
+ tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id, split: split, fee_estimator: fee_estimator)
236
+ if token_metadata
237
+ tx = Token.sign_to_p2c_output(issuer, tx, funding_tx, token_metadata.payment_base, token_metadata.metadata)
166
238
  end
239
+ tx = issuer.internal_wallet.broadcast(tx)
240
+
241
+ [color_id, tx]
167
242
  end
168
243
 
169
244
  # Send the token to other wallet
@@ -171,23 +246,21 @@ module Glueby
171
246
  # @param sender [Glueby::Wallet] wallet to send this token
172
247
  # @param receiver_address [String] address to receive this token
173
248
  # @param amount [Integer]
249
+ # @param fee_estimator [Glueby::Contract::FeeEstimator]
174
250
  # @return [Array<String, tx>] Tuple of color_id and tx object
175
251
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
176
252
  # @raise [InsufficientTokens] if wallet does not have enough token to send.
177
253
  # @raise [InvalidAmount] if amount is not positive integer.
178
- def transfer!(sender:, receiver_address:, amount: 1)
254
+ def transfer!(sender:, receiver_address:, amount: 1, fee_estimator: FeeEstimator::Fixed.new)
179
255
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
180
256
 
181
- funding_tx = create_funding_tx(wallet: sender, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider?
182
- funding_tx = sender.internal_wallet.broadcast(funding_tx) if funding_tx
183
-
184
257
  tx = create_transfer_tx(
185
- funding_tx: funding_tx,
186
258
  color_id: color_id,
187
259
  sender: sender,
188
260
  receiver_address: receiver_address,
189
261
  amount: amount,
190
- only_finalized: only_finalized?
262
+ only_finalized: only_finalized?,
263
+ fee_estimator: fee_estimator
191
264
  )
192
265
  sender.internal_wallet.broadcast(tx)
193
266
  [color_id, tx]
@@ -197,23 +270,22 @@ module Glueby
197
270
  #
198
271
  # @param sender [Glueby::Wallet] wallet to send this token
199
272
  # @param receivers [Array<Hash>] array of hash, which keys are :address and :amount
273
+ # @param fee_estimator [Glueby::Contract::FeeEstimator]
200
274
  # @return [Array<String, tx>] Tuple of color_id and tx object
201
275
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
202
276
  # @raise [InsufficientTokens] if wallet does not have enough token to send.
203
277
  # @raise [InvalidAmount] if amount is not positive integer.
204
- def multi_transfer!(sender:, receivers:)
278
+ def multi_transfer!(sender:, receivers:, fee_estimator: FeeEstimator::Fixed.new)
205
279
  receivers.each do |r|
206
280
  raise Glueby::Contract::Errors::InvalidAmount unless r[:amount].positive?
207
281
  end
208
- funding_tx = create_funding_tx(wallet: sender, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider?
209
- funding_tx = sender.internal_wallet.broadcast(funding_tx) if funding_tx
210
282
 
211
283
  tx = create_multi_transfer_tx(
212
- funding_tx: funding_tx,
213
284
  color_id: color_id,
214
285
  sender: sender,
215
286
  receivers: receivers,
216
- only_finalized: only_finalized?
287
+ only_finalized: only_finalized?,
288
+ fee_estimator: fee_estimator
217
289
  )
218
290
  sender.internal_wallet.broadcast(tx)
219
291
  [color_id, tx]
@@ -224,33 +296,17 @@ module Glueby
224
296
  #
225
297
  # @param sender [Glueby::Wallet] wallet to send this token
226
298
  # @param amount [Integer]
299
+ # @param fee_estimator [Glueby::Contract::FeeEstimator]
227
300
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
228
301
  # @raise [InsufficientTokens] if wallet does not have enough token to send transaction.
229
302
  # @raise [InvalidAmount] if amount is not positive integer.
230
- def burn!(sender:, amount: 0)
303
+ def burn!(sender:, amount: 0, fee_estimator: FeeEstimator::Fixed.new)
231
304
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
232
305
  balance = sender.balances(only_finalized?)[color_id.to_hex]
233
306
  raise Glueby::Contract::Errors::InsufficientTokens unless balance
234
307
  raise Glueby::Contract::Errors::InsufficientTokens if balance < amount
235
308
 
236
- burn_all_amount_flag = true if balance - amount == 0
237
-
238
- utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
239
- if utxo_provider
240
- funding_tx = create_funding_tx(
241
- wallet: sender,
242
- # When it burns all the amount of the color id, burn tx is not going to be have any output
243
- # because change outputs is not necessary. Transactions needs one output at least.
244
- # At that time, set true to this option to get more value to be created change output to
245
- # the tx.
246
- need_value_for_change_output: burn_all_amount_flag,
247
- only_finalized: only_finalized?
248
- )
249
- end
250
-
251
- funding_tx = sender.internal_wallet.broadcast(funding_tx) if funding_tx
252
-
253
- tx = create_burn_tx(funding_tx: funding_tx, color_id: color_id, sender: sender, amount: amount, only_finalized: only_finalized?)
309
+ tx = create_burn_tx(color_id: color_id, sender: sender, amount: amount, only_finalized: only_finalized?, fee_estimator: fee_estimator)
254
310
  sender.internal_wallet.broadcast(tx)
255
311
  end
256
312
 
@@ -264,6 +320,31 @@ module Glueby
264
320
  results.sum { |result| result[:amount] }
265
321
  end
266
322
 
323
+ # Return metadata for this token
324
+ # @return [String] metadata. if token is not associated with metadata, return nil
325
+ def metadata
326
+ token_metadata&.metadata
327
+ end
328
+
329
+ # Return pay to contract address
330
+ # @return [String] p2c_address. if token is not associated with metadata, return nil
331
+ def p2c_address
332
+ token_metadata&.p2c_address
333
+ end
334
+
335
+ # Return public key used to generate pay to contract address
336
+ # @return [String] payment_base. if token is not associated with metadata, return nil
337
+ def payment_base
338
+ token_metadata&.payment_base
339
+ end
340
+
341
+ # Return Glueby::Contract::AR::TokenMetadata instance for this token
342
+ # @return [Glueby::Contract::AR::TokenMetadata]
343
+ def token_metadata
344
+ return @token_metadata if defined? @token_metadata
345
+ @token_metadata = Glueby::Contract::AR::TokenMetadata.find_by(color_id: color_id.to_hex)
346
+ end
347
+
267
348
  # Return token type
268
349
  # @return [Tapyrus::Color::TokenTypes]
269
350
  def token_type
@@ -312,18 +393,18 @@ module Glueby
312
393
  end
313
394
 
314
395
  # Verify that wallet is the issuer of the reissuable token
315
- # reutrn [Boolean]
316
- def validate_reissuer(wallet:)
317
- addresses = wallet.internal_wallet.get_addresses
318
- addresses.each do |address|
319
- decoded_address = Tapyrus.decode_base58_address(address)
320
- pubkey_hash_from_address = decoded_address[0]
321
- pubkey_hash_from_script = Tapyrus::Script.parse_from_payload(script_pubkey.chunks[2])
322
- if pubkey_hash_from_address == pubkey_hash_from_script.to_s
323
- return true
396
+ # @param wallet [Glueby::Wallet]
397
+ # @param token_metadata [Glueby::Contract::AR::TokenMetadata] metadata to be stored in blockchain as p2c address
398
+ # @return [Boolean] true if the wallet is the issuer of this token
399
+ def valid_reissuer?(wallet:, token_metadata: nil)
400
+ return false unless script_pubkey&.p2pkh?
401
+ address = if token_metadata
402
+ payment_base = Tapyrus::Key.new(pubkey: token_metadata.payment_base, key_type: Tapyrus::Key::TYPES[:compressed])
403
+ payment_base.to_p2pkh
404
+ else
405
+ script_pubkey.to_addr
324
406
  end
325
- end
326
- false
407
+ wallet.internal_wallet.has_address?(address)
327
408
  end
328
409
  end
329
410
  end
@@ -8,16 +8,16 @@ module Glueby
8
8
  end
9
9
 
10
10
  # Create new public key, and new transaction that sends TPC to it
11
- def create_funding_tx(wallet:, script: nil, fee_estimator: FeeEstimator::Fixed.new, need_value_for_change_output: false, only_finalized: true)
11
+ def create_funding_tx(wallet:, script: nil, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
12
12
  if Glueby.configuration.use_utxo_provider?
13
- utxo_provider = UtxoProvider.new
13
+ utxo_provider = UtxoProvider.instance
14
14
  script_pubkey = script ? script : Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
15
- funding_tx, _index = utxo_provider.get_utxo(script_pubkey, funding_tx_amount(need_value_for_change_output: need_value_for_change_output))
15
+ funding_tx, _index = utxo_provider.get_utxo(script_pubkey, funding_tx_amount)
16
16
  utxo_provider.wallet.sign_tx(funding_tx)
17
17
  else
18
18
  txb = Tapyrus::TxBuilder.new
19
19
  fee = fee_estimator.fee(FeeEstimator.dummy_tx(txb.build))
20
- amount = fee + funding_tx_amount(need_value_for_change_output: need_value_for_change_output)
20
+ amount = fee + funding_tx_amount
21
21
  sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(amount, nil, only_finalized)
22
22
  outputs.each do |utxo|
23
23
  txb.add_utxo({
@@ -44,20 +44,39 @@ module Glueby
44
44
  tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
45
45
  output = funding_tx.outputs.first
46
46
 
47
- receiver_script = Tapyrus::Script.parse_from_payload(output.script_pubkey.to_payload)
48
- color_id = Tapyrus::Color::ColorIdentifier.reissuable(receiver_script)
47
+ receiver_script = Tapyrus::Script.parse_from_addr(issuer.internal_wallet.receive_address)
48
+ funding_script = Tapyrus::Script.parse_from_payload(output.script_pubkey.to_payload)
49
+ color_id = Tapyrus::Color::ColorIdentifier.reissuable(funding_script)
49
50
  receiver_colored_script = receiver_script.add_color(color_id)
50
51
 
51
52
  add_split_output(tx, amount, split, receiver_colored_script)
52
53
 
53
54
  fee = fee_estimator.fee(FeeEstimator.dummy_tx(tx))
54
- fill_change_tpc(tx, issuer, output.value - fee)
55
+
55
56
  prev_txs = [{
56
57
  txid: funding_tx.txid,
57
58
  vout: 0,
58
59
  scriptPubKey: output.script_pubkey.to_hex,
59
60
  amount: output.value
60
61
  }]
62
+
63
+ if Glueby.configuration.use_utxo_provider?
64
+ utxo_provider = UtxoProvider.instance
65
+ tx, fee, input_amount, provided_utxos = utxo_provider.fill_inputs(
66
+ tx,
67
+ target_amount: 0,
68
+ current_amount: output.value,
69
+ fee_estimator: fee_estimator
70
+ )
71
+ prev_txs.concat(provided_utxos)
72
+ else
73
+ # TODO: Support the case of providing UTXOs from sender's wallet.
74
+ input_amount = output.value
75
+ end
76
+
77
+ fill_change_tpc(tx, issuer, input_amount - fee)
78
+
79
+ UtxoProvider.instance.wallet.sign_tx(tx, prev_txs) if Glueby.configuration.use_utxo_provider?
61
80
  issuer.internal_wallet.sign_tx(tx, prev_txs)
62
81
  end
63
82
 
@@ -96,18 +115,32 @@ module Glueby
96
115
  receiver_colored_script = receiver_script.add_color(color_id)
97
116
  add_split_output(tx, amount, split, receiver_colored_script)
98
117
 
99
- fill_change_tpc(tx, issuer, sum - fee)
100
118
  prev_txs = if funding_tx
101
- output = funding_tx.outputs.first
102
- [{
103
- txid: funding_tx.txid,
104
- vout: 0,
105
- scriptPubKey: output.script_pubkey.to_hex,
106
- amount: output.value
107
- }]
108
- else
109
- []
119
+ output = funding_tx.outputs.first
120
+ [{
121
+ txid: funding_tx.txid,
122
+ vout: 0,
123
+ scriptPubKey: output.script_pubkey.to_hex,
124
+ amount: output.value
125
+ }]
126
+ else
127
+ []
128
+ end
129
+
130
+ if Glueby.configuration.use_utxo_provider?
131
+ utxo_provider = UtxoProvider.instance
132
+ tx, fee, sum, provided_utxos = utxo_provider.fill_inputs(
133
+ tx,
134
+ target_amount: 0,
135
+ current_amount: sum,
136
+ fee_estimator: fee_estimator
137
+ )
138
+ prev_txs.concat(provided_utxos)
110
139
  end
140
+
141
+ fill_change_tpc(tx, issuer, sum - fee)
142
+
143
+ UtxoProvider.instance.wallet.sign_tx(tx, prev_txs) if Glueby.configuration.use_utxo_provider?
111
144
  issuer.internal_wallet.sign_tx(tx, prev_txs)
112
145
  end
113
146
 
@@ -118,25 +151,42 @@ module Glueby
118
151
  tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
119
152
  output = funding_tx.outputs.first
120
153
 
121
- receiver_script = Tapyrus::Script.parse_from_payload(output.script_pubkey.to_payload)
154
+ receiver_script = Tapyrus::Script.parse_from_addr(issuer.internal_wallet.receive_address)
122
155
  receiver_colored_script = receiver_script.add_color(color_id)
123
156
  add_split_output(tx, amount, split, receiver_colored_script)
124
157
 
125
158
  fee = fee_estimator.fee(FeeEstimator.dummy_tx(tx))
126
- fill_change_tpc(tx, issuer, output.value - fee)
159
+
127
160
  prev_txs = [{
128
161
  txid: funding_tx.txid,
129
162
  vout: 0,
130
163
  scriptPubKey: output.script_pubkey.to_hex,
131
164
  amount: output.value
132
165
  }]
166
+
167
+ if Glueby.configuration.use_utxo_provider?
168
+ utxo_provider = UtxoProvider.instance
169
+ tx, fee, input_amount, provided_utxos = utxo_provider.fill_inputs(
170
+ tx,
171
+ target_amount: 0,
172
+ current_amount: output.value,
173
+ fee_estimator: fee_estimator
174
+ )
175
+ prev_txs.concat(provided_utxos)
176
+ else
177
+ # TODO: Support the case of providing UTXOs from sender's wallet.
178
+ input_amount = output.value
179
+ end
180
+
181
+ fill_change_tpc(tx, issuer, input_amount - fee)
182
+
183
+ UtxoProvider.instance.wallet.sign_tx(tx, prev_txs) if Glueby.configuration.use_utxo_provider?
133
184
  issuer.internal_wallet.sign_tx(tx, prev_txs)
134
185
  end
135
186
 
136
- def create_transfer_tx(funding_tx:nil, color_id:, sender:, receiver_address:, amount:, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
187
+ def create_transfer_tx(color_id:, sender:, receiver_address:, amount:, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
137
188
  receivers = [{ address: receiver_address, amount: amount }]
138
189
  create_multi_transfer_tx(
139
- funding_tx: funding_tx,
140
190
  color_id: color_id,
141
191
  sender: sender,
142
192
  receivers: receivers,
@@ -145,7 +195,7 @@ module Glueby
145
195
  )
146
196
  end
147
197
 
148
- def create_multi_transfer_tx(funding_tx:nil, color_id:, sender:, receivers:, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
198
+ def create_multi_transfer_tx(color_id:, sender:, receivers:, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
149
199
  tx = Tapyrus::Tx.new
150
200
 
151
201
  amount = receivers.reduce(0) { |sum, r| sum + r[:amount].to_i }
@@ -162,32 +212,30 @@ module Glueby
162
212
  fill_change_token(tx, sender, sum_token - amount, color_id)
163
213
 
164
214
  fee = fee_estimator.fee(FeeEstimator.dummy_tx(tx))
165
- sum_tpc = if funding_tx
166
- out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
167
- tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
168
- funding_tx.outputs.first.value
215
+
216
+ # Fill inputs for paying fee
217
+ prev_txs = []
218
+ if Glueby.configuration.use_utxo_provider?
219
+ utxo_provider = UtxoProvider.instance
220
+ tx, fee, sum_tpc, provided_utxos = utxo_provider.fill_inputs(
221
+ tx,
222
+ target_amount: 0,
223
+ current_amount: 0,
224
+ fee_estimator: fee_estimator
225
+ )
226
+ prev_txs.concat(provided_utxos)
169
227
  else
228
+ # TODO: Support the case of increasing fee by adding multiple inputs
170
229
  sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee, nil, only_finalized)
171
230
  fill_input(tx, outputs)
172
- sum_tpc
173
231
  end
174
232
 
175
233
  fill_change_tpc(tx, sender, sum_tpc - fee)
176
- prev_txs = if funding_tx
177
- output = funding_tx.outputs.first
178
- [{
179
- txid: funding_tx.txid,
180
- vout: 0,
181
- scriptPubKey: output.script_pubkey.to_hex,
182
- amount: output.value
183
- }]
184
- else
185
- []
186
- end
234
+ UtxoProvider.instance.wallet.sign_tx(tx, prev_txs) if Glueby.configuration.use_utxo_provider?
187
235
  sender.internal_wallet.sign_tx(tx, prev_txs)
188
236
  end
189
237
 
190
- def create_burn_tx(funding_tx:nil, color_id:, sender:, amount: 0, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
238
+ def create_burn_tx(color_id:, sender:, amount: 0, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
191
239
  tx = Tapyrus::Tx.new
192
240
 
193
241
  utxos = sender.internal_wallet.list_unspent(only_finalized)
@@ -195,32 +243,27 @@ module Glueby
195
243
  fill_input(tx, outputs)
196
244
 
197
245
  fill_change_token(tx, sender, sum_token - amount, color_id) if amount.positive?
198
-
199
246
  fee = fee_estimator.fee(FeeEstimator.dummy_tx(tx))
200
247
 
201
- sum_tpc = if funding_tx
202
- out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
203
- tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
204
- funding_tx.outputs.first.value
248
+ provided_utxos = []
249
+ if Glueby.configuration.use_utxo_provider?
250
+ # When it burns all the amount of the color id, burn tx is not going to have any output
251
+ # because change outputs are not necessary, though such a transaction is not 'standard' and would be rejected by the Tapyrus node.
252
+ # UtxoProvider#fill_inputs method provides an extra output with a DUST_LIMIT value in this case
253
+ # , so that it created at least one output to the burn tx.
254
+ tx, fee, sum_tpc, provided_utxos = UtxoProvider.instance.fill_inputs(
255
+ tx,
256
+ target_amount: 0,
257
+ current_amount: 0,
258
+ fee_estimator: fee_estimator
259
+ )
205
260
  else
206
261
  sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + DUST_LIMIT, nil, only_finalized)
207
262
  fill_input(tx, outputs)
208
- sum_tpc
209
263
  end
210
-
211
264
  fill_change_tpc(tx, sender, sum_tpc - fee)
212
- prev_txs = if funding_tx
213
- output = funding_tx.outputs.first
214
- [{
215
- txid: funding_tx.txid,
216
- vout: 0,
217
- scriptPubKey: output.script_pubkey.to_hex,
218
- amount: output.value
219
- }]
220
- else
221
- []
222
- end
223
- sender.internal_wallet.sign_tx(tx, prev_txs)
265
+ UtxoProvider.instance.wallet.sign_tx(tx, provided_utxos) if Glueby.configuration.use_utxo_provider?
266
+ sender.internal_wallet.sign_tx(tx, provided_utxos)
224
267
  end
225
268
 
226
269
  def add_split_output(tx, amount, split, script_pubkey)
@@ -245,7 +288,7 @@ module Glueby
245
288
  return unless change.positive?
246
289
 
247
290
  if Glueby.configuration.use_utxo_provider?
248
- change_script = Tapyrus::Script.parse_from_addr(UtxoProvider.new.wallet.change_address)
291
+ change_script = Tapyrus::Script.parse_from_addr(UtxoProvider.instance.wallet.change_address)
249
292
  else
250
293
  change_script = Tapyrus::Script.parse_from_addr(wallet.internal_wallet.change_address)
251
294
  end
@@ -294,14 +337,8 @@ module Glueby
294
337
  # The amount of output in funding tx
295
338
  # It returns same amount with FeeEstimator::Fixed's fixed fee. Because it is enough for paying fee for consumer
296
339
  # transactions of the funding transactions.
297
- #
298
- # @option [Boolean] need_value_for_change_output If it is true, adds more value than the fee for producing change output.
299
- def funding_tx_amount(need_value_for_change_output: false)
300
- if need_value_for_change_output
301
- FeeEstimator::Fixed.new.fixed_fee + DUST_LIMIT
302
- else
303
- FeeEstimator::Fixed.new.fixed_fee
304
- end
340
+ def funding_tx_amount
341
+ FeeEstimator::Fixed.new.fixed_fee
305
342
  end
306
343
  end
307
344
  end