glueby 0.6.3 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea6c9da8fdd46b8e2d82e7d403580d160f8eded28723f7ee33c14e002be5428f
4
- data.tar.gz: d74cc716d2ce35f19eb0ed5d9eb6d46cd464fa3a19a87073dd61a1e2180ef5fe
3
+ metadata.gz: '0196e03513c71e992b7b126c3100a6a57bff96210479f97736a7ea1893f029ec'
4
+ data.tar.gz: f2d3fa7ec7918394054447ccbca4dff5f039a0a8ef5d431311800dcae9fdb2e6
5
5
  SHA512:
6
- metadata.gz: 6c6c75c349edfd4c5ee5fc0e752ce9c65f1ad2f5b323d38f2a51b2ab5d91de8786daf67f1bfb1fdd221a7cc10cf891b6f6551fd58afe8059593a81536027d25c
7
- data.tar.gz: 38c2b4a8e2a085bf238ca37fd1a39930e2b337d0f80a9695adb45116dc8e2cc4478c3c0041c08b0def00cdf15235664bee9d4c6beb9e7a285c1320f8f1b768c2
6
+ metadata.gz: 9bdb76d2162a482823107ac0a61d81436599a3ce49d706c69366ac288c394546fa553e896c91a9af084a3f3991d698307a3cfcfc457b47625cef06e3f5b6c113
7
+ data.tar.gz: e1f8f4885822bc70475e99856858421e693db63b3dae96abaaac2c235d7c2fcd8ca610333a085b03a706976ffdcfffa0c4e5f9ef9f0da8e391a73ee350e93ec4
data/glueby.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ["lib"]
28
28
 
29
- spec.add_runtime_dependency 'tapyrus', '>= 0.2.9'
29
+ spec.add_runtime_dependency 'tapyrus', '>= 0.2.13'
30
30
  spec.add_runtime_dependency 'activerecord', '~> 6.1.3'
31
31
  spec.add_development_dependency 'sqlite3'
32
32
  spec.add_development_dependency 'rails', '~> 6.1.3'
@@ -3,7 +3,25 @@ module Glueby
3
3
  class SystemInformation < ::ActiveRecord::Base
4
4
 
5
5
  def self.synced_block_height
6
- SystemInformation.find_by(info_key: "synced_block_number")
6
+ find_by(info_key: "synced_block_number")
7
+ end
8
+
9
+ # Return if wallet allows to use only finalized utxo.
10
+ # @return [Boolean] true if wallet allows to use only finalized utxo, otherwise false.
11
+ def self.use_only_finalized_utxo?
12
+ find_by(info_key: "use_only_finalized_utxo")&.int_value != 0
13
+ end
14
+
15
+ # Return default value of the utxo provider
16
+ # @return [Integer] default value of utxo provider
17
+ def self.utxo_provider_default_value
18
+ find_by(info_key: "utxo_provider_default_value")&.int_value
19
+ end
20
+
21
+ # Return pool size of the utxo provider
22
+ # @return [Integer] pool size of utxo provider
23
+ def self.utxo_provider_pool_size
24
+ find_by(info_key: "utxo_provider_pool_size")&.int_value
7
25
  end
8
26
 
9
27
  def int_value
@@ -2,7 +2,10 @@ module Glueby
2
2
  module Contract
3
3
  module AR
4
4
  class Timestamp < ::ActiveRecord::Base
5
+ include Glueby::GluebyLogger
5
6
  include Glueby::Contract::Timestamp::Util
7
+ include Glueby::Contract::TxBuilder
8
+
6
9
  enum status: { init: 0, unconfirmed: 1, confirmed: 2 }
7
10
  enum timestamp_type: { simple: 0, trackable: 1 }
8
11
 
@@ -21,6 +24,33 @@ module Glueby
21
24
  def latest
22
25
  trackable?
23
26
  end
27
+
28
+ # Broadcast and save timestamp
29
+ # @param [Glueby::Contract::FixedFeeEstimator] fee_estimator
30
+ # @return true if tapyrus transactions were broadcasted and the timestamp was updated successfully, otherwise false.
31
+ def save_with_broadcast(fee_estimator: Glueby::Contract::FixedFeeEstimator.new)
32
+ utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
33
+ wallet = Glueby::Wallet.load(wallet_id)
34
+ funding_tx, tx, p2c_address, payment_base = create_txs(wallet, prefix, content_hash, fee_estimator, utxo_provider, type: timestamp_type.to_sym)
35
+ if funding_tx
36
+ ::ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
37
+ wallet.internal_wallet.broadcast(funding_tx)
38
+ end
39
+ logger.info("funding tx was broadcasted(id=#{id}, funding_tx.txid=#{funding_tx.txid})")
40
+ end
41
+ ::ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
42
+ wallet.internal_wallet.broadcast(tx) do |tx|
43
+ assign_attributes(txid: tx.txid, status: :unconfirmed, p2c_address: p2c_address, payment_base: payment_base)
44
+ save!
45
+ end
46
+ end
47
+ logger.info("timestamp tx was broadcasted (id=#{id}, txid=#{tx.txid})")
48
+ true
49
+ rescue => e
50
+ logger.error("failed to broadcast (id=#{id}, reason=#{e.message})")
51
+ errors.add(:base, "failed to broadcast (id=#{id}, reason=#{e.message})")
52
+ false
53
+ end
24
54
  end
25
55
  end
26
56
  end
@@ -4,6 +4,7 @@ module Glueby
4
4
  class InsufficientFunds < StandardError; end
5
5
  class InsufficientTokens < StandardError; end
6
6
  class InvalidAmount < StandardError; end
7
+ class InvalidSplit < StandardError; end
7
8
  class InvalidTokenType < StandardError; end
8
9
  class InvalidTimestampType < StandardError; end
9
10
  class TxAlreadyBroadcasted < StandardError; end
@@ -56,18 +56,21 @@ module Glueby
56
56
  # @param issuer [Glueby::Wallet]
57
57
  # @param token_type [TokenTypes]
58
58
  # @param amount [Integer]
59
+ # @param split [Integer] The tx outputs should be split by specified number.
59
60
  # @return [Array<token, Array<tx>>] Tuple of tx array and token object
60
61
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
61
62
  # @raise [InvalidAmount] if amount is not positive integer.
63
+ # @raise [InvalidSplit] if split is greater than 1 for NFT token.
62
64
  # @raise [UnspportedTokenType] if token is not supported.
63
- def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1)
65
+ def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1, split: 1)
64
66
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
67
+ raise Glueby::Contract::Errors::InvalidSplit if token_type == Tapyrus::Color::TokenTypes::NFT && split > 1
65
68
 
66
69
  txs, color_id = case token_type
67
70
  when Tapyrus::Color::TokenTypes::REISSUABLE
68
- issue_reissuable_token(issuer: issuer, amount: amount)
71
+ issue_reissuable_token(issuer: issuer, amount: amount, split: split)
69
72
  when Tapyrus::Color::TokenTypes::NON_REISSUABLE
70
- issue_non_reissuable_token(issuer: issuer, amount: amount)
73
+ issue_non_reissuable_token(issuer: issuer, amount: amount, split: split)
71
74
  when Tapyrus::Color::TokenTypes::NFT
72
75
  issue_nft_token(issuer: issuer)
73
76
  else
@@ -77,29 +80,36 @@ module Glueby
77
80
  [new(color_id: color_id), txs]
78
81
  end
79
82
 
83
+ def only_finalized?
84
+ Glueby::AR::SystemInformation.use_only_finalized_utxo?
85
+ end
86
+
80
87
  private
81
88
 
82
- def issue_reissuable_token(issuer:, amount:)
83
- funding_tx = create_funding_tx(wallet: issuer)
89
+ def issue_reissuable_token(issuer:, amount:, split: 1)
90
+ funding_tx = create_funding_tx(wallet: issuer, only_finalized: only_finalized?)
84
91
  script_pubkey = funding_tx.outputs.first.script_pubkey
85
92
  color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
86
93
 
94
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
95
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
96
+ end
97
+
87
98
  ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
88
99
  # Store the script_pubkey for reissue the token.
89
100
  Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
90
101
 
91
- funding_tx = issuer.internal_wallet.broadcast(funding_tx)
92
- tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
102
+ tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount, split: split)
93
103
  tx = issuer.internal_wallet.broadcast(tx)
94
104
  [[funding_tx, tx], color_id]
95
105
  end
96
106
  end
97
107
 
98
- def issue_non_reissuable_token(issuer:, amount:)
99
- funding_tx = create_funding_tx(wallet: issuer) if Glueby.configuration.use_utxo_provider?
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?
100
110
  funding_tx = issuer.internal_wallet.broadcast(funding_tx) if funding_tx
101
111
 
102
- tx = create_issue_tx_for_non_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
112
+ tx = create_issue_tx_for_non_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount, split: split)
103
113
  tx = issuer.internal_wallet.broadcast(tx)
104
114
 
105
115
  out_point = tx.inputs.first.out_point
@@ -112,7 +122,7 @@ module Glueby
112
122
  end
113
123
 
114
124
  def issue_nft_token(issuer:)
115
- funding_tx = create_funding_tx(wallet: issuer) if Glueby.configuration.use_utxo_provider?
125
+ funding_tx = create_funding_tx(wallet: issuer, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider?
116
126
  funding_tx = issuer.internal_wallet.broadcast(funding_tx) if funding_tx
117
127
 
118
128
  tx = create_issue_tx_for_nft_token(funding_tx: funding_tx, issuer: issuer)
@@ -134,19 +144,20 @@ module Glueby
134
144
  # A wallet can issue the token only when it is REISSUABLE token.
135
145
  # @param issuer [Glueby::Wallet]
136
146
  # @param amount [Integer]
147
+ # @param split [Integer]
137
148
  # @return [Array<String, tx>] Tuple of color_id and tx object
138
149
  # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
139
150
  # @raise [InvalidAmount] if amount is not positive integer.
140
151
  # @raise [InvalidTokenType] if token is not reissuable.
141
152
  # @raise [UnknownScriptPubkey] when token is reissuable but it doesn't know script pubkey to issue token.
142
- def reissue!(issuer:, amount:)
153
+ def reissue!(issuer:, amount:, split: 1)
143
154
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
144
155
  raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
145
156
 
146
157
  if validate_reissuer(wallet: issuer)
147
- funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey)
158
+ funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey, only_finalized: only_finalized?)
148
159
  funding_tx = issuer.internal_wallet.broadcast(funding_tx)
149
- tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
160
+ tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id, split: split)
150
161
  tx = issuer.internal_wallet.broadcast(tx)
151
162
 
152
163
  [color_id, tx]
@@ -167,10 +178,43 @@ module Glueby
167
178
  def transfer!(sender:, receiver_address:, amount: 1)
168
179
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
169
180
 
170
- funding_tx = create_funding_tx(wallet: sender) if Glueby.configuration.use_utxo_provider?
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
+ tx = create_transfer_tx(
185
+ funding_tx: funding_tx,
186
+ color_id: color_id,
187
+ sender: sender,
188
+ receiver_address: receiver_address,
189
+ amount: amount,
190
+ only_finalized: only_finalized?
191
+ )
192
+ sender.internal_wallet.broadcast(tx)
193
+ [color_id, tx]
194
+ end
195
+
196
+ # Send the tokens to multiple wallets
197
+ #
198
+ # @param sender [Glueby::Wallet] wallet to send this token
199
+ # @param receivers [Array<Hash>] array of hash, which keys are :address and :amount
200
+ # @return [Array<String, tx>] Tuple of color_id and tx object
201
+ # @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
202
+ # @raise [InsufficientTokens] if wallet does not have enough token to send.
203
+ # @raise [InvalidAmount] if amount is not positive integer.
204
+ def multi_transfer!(sender:, receivers:)
205
+ receivers.each do |r|
206
+ raise Glueby::Contract::Errors::InvalidAmount unless r[:amount].positive?
207
+ end
208
+ funding_tx = create_funding_tx(wallet: sender, only_finalized: only_finalized?) if Glueby.configuration.use_utxo_provider?
171
209
  funding_tx = sender.internal_wallet.broadcast(funding_tx) if funding_tx
172
210
 
173
- tx = create_transfer_tx(funding_tx: funding_tx, color_id: color_id, sender: sender, receiver_address: receiver_address, amount: amount)
211
+ tx = create_multi_transfer_tx(
212
+ funding_tx: funding_tx,
213
+ color_id: color_id,
214
+ sender: sender,
215
+ receivers: receivers,
216
+ only_finalized: only_finalized?
217
+ )
174
218
  sender.internal_wallet.broadcast(tx)
175
219
  [color_id, tx]
176
220
  end
@@ -185,7 +229,7 @@ module Glueby
185
229
  # @raise [InvalidAmount] if amount is not positive integer.
186
230
  def burn!(sender:, amount: 0)
187
231
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
188
- balance = sender.balances[color_id.to_hex]
232
+ balance = sender.balances(only_finalized?)[color_id.to_hex]
189
233
  raise Glueby::Contract::Errors::InsufficientTokens unless balance
190
234
  raise Glueby::Contract::Errors::InsufficientTokens if balance < amount
191
235
 
@@ -199,13 +243,14 @@ module Glueby
199
243
  # because change outputs is not necessary. Transactions needs one output at least.
200
244
  # At that time, set true to this option to get more value to be created change output to
201
245
  # the tx.
202
- need_value_for_change_output: burn_all_amount_flag
246
+ need_value_for_change_output: burn_all_amount_flag,
247
+ only_finalized: only_finalized?
203
248
  )
204
249
  end
205
250
 
206
251
  funding_tx = sender.internal_wallet.broadcast(funding_tx) if funding_tx
207
252
 
208
- tx = create_burn_tx(funding_tx: funding_tx, color_id: color_id, sender: sender, amount: amount)
253
+ tx = create_burn_tx(funding_tx: funding_tx, color_id: color_id, sender: sender, amount: amount, only_finalized: only_finalized?)
209
254
  sender.internal_wallet.broadcast(tx)
210
255
  end
211
256
 
@@ -214,7 +259,7 @@ module Glueby
214
259
  # @return [Integer] amount of utxo value associated with this token.
215
260
  def amount(wallet:)
216
261
  # collect utxo associated with this address
217
- utxos = wallet.internal_wallet.list_unspent
262
+ utxos = wallet.internal_wallet.list_unspent(only_finalized?)
218
263
  _, results = collect_colored_outputs(utxos, color_id)
219
264
  results.sum { |result| result[:amount] }
220
265
  end
@@ -262,6 +307,10 @@ module Glueby
262
307
 
263
308
  private
264
309
 
310
+ def only_finalized?
311
+ @only_finalized ||= Token.only_finalized?
312
+ end
313
+
265
314
  # Verify that wallet is the issuer of the reissuable token
266
315
  # reutrn [Boolean]
267
316
  def validate_reissuer(wallet:)
@@ -8,7 +8,7 @@ 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: FixedFeeEstimator.new, need_value_for_change_output: false)
11
+ def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new, need_value_for_change_output: false, only_finalized: true)
12
12
  if Glueby.configuration.use_utxo_provider?
13
13
  utxo_provider = UtxoProvider.new
14
14
  script_pubkey = script ? script : Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
@@ -17,8 +17,8 @@ module Glueby
17
17
  else
18
18
  txb = Tapyrus::TxBuilder.new
19
19
  fee = fee_estimator.fee(dummy_tx(txb.build))
20
-
21
- sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + funding_tx_amount(need_value_for_change_output: need_value_for_change_output))
20
+ amount = fee + funding_tx_amount(need_value_for_change_output: need_value_for_change_output)
21
+ sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(amount, nil, only_finalized)
22
22
  outputs.each do |utxo|
23
23
  txb.add_utxo({
24
24
  script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
@@ -37,7 +37,7 @@ module Glueby
37
37
  end
38
38
  end
39
39
 
40
- def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
40
+ def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, split: 1, fee_estimator: FixedFeeEstimator.new)
41
41
  tx = Tapyrus::Tx.new
42
42
 
43
43
  out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
@@ -47,7 +47,8 @@ module Glueby
47
47
  receiver_script = Tapyrus::Script.parse_from_payload(output.script_pubkey.to_payload)
48
48
  color_id = Tapyrus::Color::ColorIdentifier.reissuable(receiver_script)
49
49
  receiver_colored_script = receiver_script.add_color(color_id)
50
- tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
50
+
51
+ add_split_output(tx, amount, split, receiver_colored_script)
51
52
 
52
53
  fee = fee_estimator.fee(dummy_tx(tx))
53
54
  fill_change_tpc(tx, issuer, output.value - fee)
@@ -60,15 +61,15 @@ module Glueby
60
61
  issuer.internal_wallet.sign_tx(tx, prev_txs)
61
62
  end
62
63
 
63
- def create_issue_tx_for_non_reissuable_token(funding_tx: nil, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
64
- create_issue_tx_from_out_point(funding_tx: funding_tx, token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, fee_estimator: fee_estimator)
64
+ def create_issue_tx_for_non_reissuable_token(funding_tx: nil, issuer:, amount:, split: 1, fee_estimator: FixedFeeEstimator.new, only_finalized: true)
65
+ create_issue_tx_from_out_point(funding_tx: funding_tx, token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, split: split, fee_estimator: fee_estimator, only_finalized: only_finalized)
65
66
  end
66
67
 
67
- def create_issue_tx_for_nft_token(funding_tx: nil, issuer:, fee_estimator: FixedFeeEstimator.new)
68
- create_issue_tx_from_out_point(funding_tx: funding_tx, token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_estimator: fee_estimator)
68
+ def create_issue_tx_for_nft_token(funding_tx: nil, issuer:, fee_estimator: FixedFeeEstimator.new, only_finalized: true)
69
+ create_issue_tx_from_out_point(funding_tx: funding_tx, token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_estimator: fee_estimator, only_finalized: only_finalized)
69
70
  end
70
71
 
71
- def create_issue_tx_from_out_point(funding_tx: nil, token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
72
+ def create_issue_tx_from_out_point(funding_tx: nil, token_type:, issuer:, amount:, split: 1, fee_estimator: FixedFeeEstimator.new, only_finalized: true)
72
73
  tx = Tapyrus::Tx.new
73
74
 
74
75
  fee = fee_estimator.fee(dummy_issue_tx_from_out_point)
@@ -77,7 +78,7 @@ module Glueby
77
78
  tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
78
79
  funding_tx.outputs.first.value
79
80
  else
80
- sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee)
81
+ sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee, nil, only_finalized)
81
82
  fill_input(tx, outputs)
82
83
  sum
83
84
  end
@@ -93,7 +94,7 @@ module Glueby
93
94
 
94
95
  receiver_script = Tapyrus::Script.parse_from_addr(issuer.internal_wallet.receive_address)
95
96
  receiver_colored_script = receiver_script.add_color(color_id)
96
- tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
97
+ add_split_output(tx, amount, split, receiver_colored_script)
97
98
 
98
99
  fill_change_tpc(tx, issuer, sum - fee)
99
100
  prev_txs = if funding_tx
@@ -110,7 +111,7 @@ module Glueby
110
111
  issuer.internal_wallet.sign_tx(tx, prev_txs)
111
112
  end
112
113
 
113
- def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_estimator: FixedFeeEstimator.new)
114
+ def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, split: 1, fee_estimator: FixedFeeEstimator.new)
114
115
  tx = Tapyrus::Tx.new
115
116
 
116
117
  out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
@@ -119,7 +120,7 @@ module Glueby
119
120
 
120
121
  receiver_script = Tapyrus::Script.parse_from_payload(output.script_pubkey.to_payload)
121
122
  receiver_colored_script = receiver_script.add_color(color_id)
122
- tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
123
+ add_split_output(tx, amount, split, receiver_colored_script)
123
124
 
124
125
  fee = fee_estimator.fee(dummy_tx(tx))
125
126
  fill_change_tpc(tx, issuer, output.value - fee)
@@ -132,16 +133,31 @@ module Glueby
132
133
  issuer.internal_wallet.sign_tx(tx, prev_txs)
133
134
  end
134
135
 
135
- def create_transfer_tx(funding_tx:nil, color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
136
+ def create_transfer_tx(funding_tx:nil, color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new, only_finalized: true)
137
+ receivers = [{ address: receiver_address, amount: amount }]
138
+ create_multi_transfer_tx(
139
+ funding_tx: funding_tx,
140
+ color_id: color_id,
141
+ sender: sender,
142
+ receivers: receivers,
143
+ fee_estimator: fee_estimator,
144
+ only_finalized: only_finalized
145
+ )
146
+ end
147
+
148
+ def create_multi_transfer_tx(funding_tx:nil, color_id:, sender:, receivers:, fee_estimator: FixedFeeEstimator.new, only_finalized: true)
136
149
  tx = Tapyrus::Tx.new
137
150
 
138
- utxos = sender.internal_wallet.list_unspent
151
+ amount = receivers.reduce(0) { |sum, r| sum + r[:amount].to_i }
152
+ utxos = sender.internal_wallet.list_unspent(only_finalized)
139
153
  sum_token, outputs = collect_colored_outputs(utxos, color_id, amount)
140
154
  fill_input(tx, outputs)
141
155
 
142
- receiver_script = Tapyrus::Script.parse_from_addr(receiver_address)
143
- receiver_colored_script = receiver_script.add_color(color_id)
144
- tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
156
+ receivers.each do |r|
157
+ receiver_script = Tapyrus::Script.parse_from_addr(r[:address])
158
+ receiver_colored_script = receiver_script.add_color(color_id)
159
+ tx.outputs << Tapyrus::TxOut.new(value: r[:amount].to_i, script_pubkey: receiver_colored_script)
160
+ end
145
161
 
146
162
  fill_change_token(tx, sender, sum_token - amount, color_id)
147
163
 
@@ -151,7 +167,7 @@ module Glueby
151
167
  tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
152
168
  funding_tx.outputs.first.value
153
169
  else
154
- sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee)
170
+ sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee, nil, only_finalized)
155
171
  fill_input(tx, outputs)
156
172
  sum_tpc
157
173
  end
@@ -171,10 +187,10 @@ module Glueby
171
187
  sender.internal_wallet.sign_tx(tx, prev_txs)
172
188
  end
173
189
 
174
- def create_burn_tx(funding_tx:nil, color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
190
+ def create_burn_tx(funding_tx:nil, color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new, only_finalized: true)
175
191
  tx = Tapyrus::Tx.new
176
192
 
177
- utxos = sender.internal_wallet.list_unspent
193
+ utxos = sender.internal_wallet.list_unspent(only_finalized)
178
194
  sum_token, outputs = collect_colored_outputs(utxos, color_id, amount)
179
195
  fill_input(tx, outputs)
180
196
 
@@ -187,7 +203,7 @@ module Glueby
187
203
  tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
188
204
  funding_tx.outputs.first.value
189
205
  else
190
- sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + DUST_LIMIT)
206
+ sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + DUST_LIMIT, nil, only_finalized)
191
207
  fill_input(tx, outputs)
192
208
  sum_tpc
193
209
  end
@@ -207,6 +223,17 @@ module Glueby
207
223
  sender.internal_wallet.sign_tx(tx, prev_txs)
208
224
  end
209
225
 
226
+ def add_split_output(tx, amount, split, script_pubkey)
227
+ if amount < split
228
+ split = amount
229
+ value = 1
230
+ else
231
+ value = (amount/split).to_i
232
+ end
233
+ (split - 1).times { tx.outputs << Tapyrus::TxOut.new(value: value, script_pubkey: script_pubkey) }
234
+ tx.outputs << Tapyrus::TxOut.new(value: amount - value * (split - 1), script_pubkey: script_pubkey)
235
+ end
236
+
210
237
  def fill_input(tx, outputs)
211
238
  outputs.each do |output|
212
239
  out_point = Tapyrus::OutPoint.new(output[:txid].rhex, output[:vout])
@@ -30,14 +30,14 @@ module Glueby
30
30
 
31
31
  utxos.each { |utxo| txb.add_utxo(utxo) }
32
32
 
33
- shortage = [utxo_provider.utxo_pool_size - current_utxo_pool_size, 0].max
33
+ shortage = [utxo_provider.utxo_pool_size - utxo_provider.current_utxo_pool_size, 0].max
34
34
  return if shortage == 0
35
35
 
36
36
  added_outputs = 0
37
37
  shortage.times do
38
38
  fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
39
39
  break if (sum - fee) < utxo_provider.default_value
40
- txb.pay(address, utxo_provider.default_value)
40
+ txb.pay(utxo_provider.address, utxo_provider.default_value)
41
41
  sum -= utxo_provider.default_value
42
42
  added_outputs += 1
43
43
  end
@@ -45,11 +45,11 @@ module Glueby
45
45
  return if added_outputs == 0
46
46
 
47
47
  fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
48
- tx = txb.change_address(address)
48
+ tx = txb.change_address(utxo_provider.address)
49
49
  .fee(fee)
50
50
  .build
51
- tx = wallet.sign_tx(tx)
52
- wallet.broadcast(tx)
51
+ tx = utxo_provider.wallet.sign_tx(tx)
52
+ utxo_provider.wallet.broadcast(tx)
53
53
  ensure
54
54
  status
55
55
  end
@@ -58,13 +58,13 @@ module Glueby
58
58
  def status
59
59
  status = :ready
60
60
 
61
- if current_utxo_pool_size < utxo_provider.utxo_pool_size
62
- if tpc_amount < value_to_fill_utxo_pool
61
+ if utxo_provider.current_utxo_pool_size < utxo_provider.utxo_pool_size
62
+ if utxo_provider.tpc_amount < utxo_provider.value_to_fill_utxo_pool
63
63
  status = :insufficient_amount
64
64
  message = <<~MESSAGE
65
65
  1. Please replenishment TPC which is for paying tpc to UtxoProvider.
66
- UtxoProvider needs #{value_to_fill_utxo_pool} tapyrus in UTXO pool.
67
- UtxoProvider wallet's address is '#{address}'
66
+ UtxoProvider needs #{utxo_provider.value_to_fill_utxo_pool} tapyrus in UTXO pool.
67
+ UtxoProvider wallet's address is '#{utxo_provider.address}'
68
68
  2. Then create UTXOs for paying in UTXO pool with 'rake glueby:utxo_provider:manage_utxo_pool'
69
69
  MESSAGE
70
70
  else
@@ -72,12 +72,12 @@ module Glueby
72
72
  end
73
73
  end
74
74
 
75
- status = :not_ready if current_utxo_pool_size == 0
75
+ status = :not_ready if utxo_provider.current_utxo_pool_size == 0
76
76
 
77
77
  puts <<~EOS
78
78
  Status: #{STATUS[status]}
79
- TPC amount: #{delimit(tpc_amount)}
80
- UTXO pool size: #{delimit(current_utxo_pool_size)}
79
+ TPC amount: #{delimit(utxo_provider.tpc_amount)}
80
+ UTXO pool size: #{delimit(utxo_provider.current_utxo_pool_size)}
81
81
  #{"\n" if message}#{message}
82
82
  Configuration:
83
83
  default_value = #{delimit(utxo_provider.default_value)}
@@ -87,17 +87,13 @@ module Glueby
87
87
 
88
88
  # Show the address of Utxo Provider
89
89
  def print_address
90
- puts address
90
+ puts utxo_provider.address
91
91
  end
92
92
 
93
93
  private
94
94
 
95
- def tpc_amount
96
- wallet.balance(false)
97
- end
98
-
99
95
  def collect_outputs
100
- wallet.list_unspent.inject([0, []]) do |sum, output|
96
+ utxo_provider.wallet.list_unspent.inject([0, []]) do |sum, output|
101
97
  next sum if output[:color_id] || output[:amount] == utxo_provider.default_value
102
98
 
103
99
  new_sum = sum[0] + output[:amount]
@@ -105,34 +101,16 @@ module Glueby
105
101
  txid: output[:txid],
106
102
  script_pubkey: output[:script_pubkey],
107
103
  value: output[:amount],
108
- index: output[:vout] ,
104
+ index: output[:vout],
109
105
  finalized: output[:finalized]
110
106
  }
111
107
  [new_sum, new_outputs]
112
108
  end
113
109
  end
114
110
 
115
- def current_utxo_pool_size
116
- wallet
117
- .list_unspent(false)
118
- .count { |o| !o[:color_id] && o[:amount] == utxo_provider.default_value }
119
- end
120
-
121
- def value_to_fill_utxo_pool
122
- utxo_provider.default_value * utxo_provider.utxo_pool_size
123
- end
124
-
125
- def wallet
126
- utxo_provider.wallet
127
- end
128
-
129
111
  def delimit(num)
130
112
  num.to_s.reverse.scan(/.{1,3}/).join('_').reverse
131
113
  end
132
-
133
- def address
134
- @address ||= wallet.get_addresses.first || wallet.receive_address
135
- end
136
114
  end
137
115
  end
138
116
  end
@@ -25,11 +25,9 @@ module Glueby
25
25
  @wallet = load_wallet
26
26
  validate_config!
27
27
  @fee_estimator = (UtxoProvider.config && UtxoProvider.config[:fee_estimator]) || Glueby::Contract::FixedFeeEstimator.new
28
- @default_value = (UtxoProvider.config && UtxoProvider.config[:default_value]) || DEFAULT_VALUE
29
- @utxo_pool_size = (UtxoProvider.config && UtxoProvider.config[:utxo_pool_size]) || DEFAULT_UTXO_POOL_SIZE
30
28
  end
31
29
 
32
- attr_reader :wallet, :fee_estimator, :default_value, :utxo_pool_size
30
+ attr_reader :wallet, :fee_estimator, :address
33
31
 
34
32
  # Provide a UTXO
35
33
  # @param [Tapyrus::Script] script_pubkey The script to be provided
@@ -62,6 +60,42 @@ module Glueby
62
60
  [signed_tx, 0]
63
61
  end
64
62
 
63
+ def default_value
64
+ @default_value ||=
65
+ (
66
+ Glueby::AR::SystemInformation.utxo_provider_default_value ||
67
+ (UtxoProvider.config && UtxoProvider.config[:default_value]) ||
68
+ DEFAULT_VALUE
69
+ )
70
+ end
71
+
72
+ def utxo_pool_size
73
+ @utxo_pool_size ||=
74
+ (
75
+ Glueby::AR::SystemInformation.utxo_provider_pool_size ||
76
+ (UtxoProvider.config && UtxoProvider.config[:utxo_pool_size]) ||
77
+ DEFAULT_UTXO_POOL_SIZE
78
+ )
79
+ end
80
+
81
+ def tpc_amount
82
+ wallet.balance(false)
83
+ end
84
+
85
+ def current_utxo_pool_size
86
+ wallet
87
+ .list_unspent(false)
88
+ .count { |o| !o[:color_id] && o[:amount] == default_value }
89
+ end
90
+
91
+ def address
92
+ @address ||= wallet.get_addresses.first || wallet.receive_address
93
+ end
94
+
95
+ def value_to_fill_utxo_pool
96
+ default_value * utxo_pool_size
97
+ end
98
+
65
99
  private
66
100
 
67
101
  # Create wallet for provider
@@ -74,7 +108,7 @@ module Glueby
74
108
  end
75
109
 
76
110
  def collect_uncolored_outputs(wallet, amount)
77
- utxos = wallet.list_unspent.select { |o| !o[:color_id] && o[:amount] == @default_value }
111
+ utxos = wallet.list_unspent.select { |o| !o[:color_id] && o[:amount] == default_value }
78
112
  utxos.shuffle!
79
113
 
80
114
  utxos.inject([0, []]) do |sum, output|
@@ -88,11 +122,8 @@ module Glueby
88
122
  end
89
123
 
90
124
  def validate_config!
91
- if UtxoProvider.config
92
- utxo_pool_size = UtxoProvider.config[:utxo_pool_size]
93
- if utxo_pool_size && (!utxo_pool_size.is_a?(Integer) || utxo_pool_size > MAX_UTXO_POOL_SIZE)
94
- raise Glueby::Configuration::Errors::InvalidConfiguration, "utxo_pool_size(#{utxo_pool_size}) should not be greater than #{MAX_UTXO_POOL_SIZE}"
95
- end
125
+ if !utxo_pool_size.is_a?(Integer) || utxo_pool_size > MAX_UTXO_POOL_SIZE
126
+ raise Glueby::Configuration::Errors::InvalidConfiguration, "utxo_pool_size(#{utxo_pool_size}) should not be greater than #{MAX_UTXO_POOL_SIZE}"
96
127
  end
97
128
  end
98
129
  end
@@ -1,3 +1,3 @@
1
1
  module Glueby
2
- VERSION = "0.6.3"
2
+ VERSION = "0.8.1"
3
3
  end
data/lib/glueby.rb CHANGED
@@ -17,6 +17,16 @@ module Glueby
17
17
  require 'glueby/railtie'
18
18
  end
19
19
 
20
+ module GluebyLogger
21
+ def logger
22
+ if defined?(Rails)
23
+ Rails.logger
24
+ else
25
+ Logger.new(STDOUT)
26
+ end
27
+ end
28
+ end
29
+
20
30
  # Add prefix to activerecord table names
21
31
  def self.table_name_prefix
22
32
  'glueby_'
@@ -8,27 +8,8 @@ module Glueby
8
8
 
9
9
  def create
10
10
  timestamps = Glueby::Contract::AR::Timestamp.where(status: :init)
11
- utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
12
- timestamps.each do |t|
13
- begin
14
- wallet = Glueby::Wallet.load(t.wallet_id)
15
- funding_tx, tx, p2c_address, payment_base = create_txs(wallet, t.prefix, t.content_hash, Glueby::Contract::FixedFeeEstimator.new, utxo_provider, type: t.timestamp_type.to_sym)
16
- if funding_tx
17
- ::ActiveRecord::Base.transaction do
18
- wallet.internal_wallet.broadcast(funding_tx)
19
- puts "funding tx was broadcasted(id=#{t.id}, funding_tx.txid=#{funding_tx.txid})"
20
- end
21
- end
22
- ::ActiveRecord::Base.transaction do
23
- wallet.internal_wallet.broadcast(tx) do |tx|
24
- t.update(txid: tx.txid, status: :unconfirmed, p2c_address: p2c_address, payment_base: payment_base)
25
- end
26
- puts "timestamp tx was broadcasted (id=#{t.id}, txid=#{tx.txid})"
27
- end
28
- rescue => e
29
- puts "failed to broadcast (id=#{t.id}, reason=#{e.message})"
30
- end
31
- end
11
+ fee_estimator = Glueby::Contract::FixedFeeEstimator.new
12
+ timestamps.each { |t| t.save_with_broadcast(fee_estimator: fee_estimator) }
32
13
  end
33
14
  end
34
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glueby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-22 00:00:00.000000000 Z
11
+ date: 2022-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tapyrus
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.9
19
+ version: 0.2.13
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.9
26
+ version: 0.2.13
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement