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 +4 -4
- data/glueby.gemspec +1 -1
- data/lib/glueby/active_record/system_information.rb +19 -1
- data/lib/glueby/contract/active_record/timestamp.rb +30 -0
- data/lib/glueby/contract/errors.rb +1 -0
- data/lib/glueby/contract/token.rb +69 -20
- data/lib/glueby/contract/tx_builder.rb +50 -23
- data/lib/glueby/utxo_provider/tasks.rb +15 -37
- data/lib/glueby/utxo_provider.rb +40 -9
- data/lib/glueby/version.rb +1 -1
- data/lib/glueby.rb +10 -0
- data/lib/tasks/glueby/contract/timestamp.rake +2 -21
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0196e03513c71e992b7b126c3100a6a57bff96210479f97736a7ea1893f029ec'
|
4
|
+
data.tar.gz: f2d3fa7ec7918394054447ccbca4dff5f039a0a8ef5d431311800dcae9fdb2e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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
|
-
|
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 =
|
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(
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
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
|
data/lib/glueby/utxo_provider.rb
CHANGED
@@ -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, :
|
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] ==
|
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
|
92
|
-
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
|
data/lib/glueby/version.rb
CHANGED
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
|
-
|
12
|
-
timestamps.each
|
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.
|
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:
|
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.
|
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.
|
26
|
+
version: 0.2.13
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|