glueby 1.1.2 → 1.2.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +2 -2
- data/.ruby-version +1 -1
- data/README.md +1 -1
- data/glueby.gemspec +3 -3
- data/lib/generators/glueby/contract/templates/initializer.rb.erb +8 -8
- data/lib/generators/glueby/contract/templates/key_table.rb.erb +16 -16
- data/lib/glueby/block_syncer.rb +97 -97
- data/lib/glueby/contract/active_record/timestamp.rb +0 -3
- data/lib/glueby/contract/payment.rb +10 -15
- data/lib/glueby/contract/timestamp/tx_builder/simple.rb +19 -57
- data/lib/glueby/contract/timestamp/tx_builder/trackable.rb +6 -0
- data/lib/glueby/contract/timestamp/tx_builder/updating_trackable.rb +3 -2
- data/lib/glueby/contract/token.rb +163 -86
- data/lib/glueby/contract/tx_builder.rb +1 -0
- data/lib/glueby/internal/contract_builder.rb +462 -0
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +2 -0
- data/lib/glueby/internal/wallet/active_record/utxo.rb +3 -2
- data/lib/glueby/internal/wallet.rb +149 -12
- data/lib/glueby/internal.rb +1 -0
- data/lib/glueby/utxo_provider/tasks.rb +0 -2
- data/lib/glueby/utxo_provider.rb +15 -34
- data/lib/glueby/version.rb +1 -1
- data/lib/tasks/glueby/contract/timestamp.rake +0 -1
- metadata +14 -13
@@ -0,0 +1,462 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Internal
|
3
|
+
class ContractBuilder < Tapyrus::TxBuilder
|
4
|
+
attr_reader :fee_estimator, :sender_wallet, :prev_txs, :p2c_utxos, :use_unfinalized_utxo, :use_auto_fulfill_inputs
|
5
|
+
|
6
|
+
# @param [Glueby::Internal::Wallet] sender_wallet The wallet that is an user's wallet who send the transaction
|
7
|
+
# to a blockchain.
|
8
|
+
# @param [Symbol|Glueby::Contract::FeeEstimator] fee_estimator :auto or :fixed
|
9
|
+
# @param [Boolean] use_auto_fulfill_inputs
|
10
|
+
# If it's true, inputs are automatically added up to fulfill the TPC and tokens requirement that is added by
|
11
|
+
# #pay and #burn. The option also support to fill adding TPC inputs for paying fee.
|
12
|
+
# If Glueby.configuration.use_utxo_provider? is true, All the TPC inputs are added from the UtxoProvider's
|
13
|
+
# wallet. It it's false, all the TPC inputs are from sender_wallet.
|
14
|
+
# If Glueby.configuration.fee_provider_bears? is true, it won't add TPC amount for fee.
|
15
|
+
# @param [Boolean] use_unfinalized_utxo If it's true, The ContractBuilder use unfinalized UTXO that is not
|
16
|
+
# included in the block in its inputs.
|
17
|
+
# @raise [Glueby::ArgumentError] If the fee_estimator is not :auto or :fixed
|
18
|
+
def initialize(
|
19
|
+
sender_wallet:,
|
20
|
+
fee_estimator: :auto,
|
21
|
+
use_auto_fulfill_inputs: false,
|
22
|
+
use_unfinalized_utxo: false
|
23
|
+
)
|
24
|
+
@sender_wallet = sender_wallet
|
25
|
+
set_fee_estimator(fee_estimator)
|
26
|
+
@use_auto_fulfill_inputs = use_auto_fulfill_inputs
|
27
|
+
@use_unfinalized_utxo = use_unfinalized_utxo
|
28
|
+
@p2c_utxos = []
|
29
|
+
@prev_txs = []
|
30
|
+
@change_script_pubkeys = {}
|
31
|
+
@burn_contract = false
|
32
|
+
@issues = Hash.new(0)
|
33
|
+
super()
|
34
|
+
end
|
35
|
+
|
36
|
+
# Issue reissuable token
|
37
|
+
# @param script_pubkey [Tapyrus::Script] the script pubkey in the issue input.
|
38
|
+
# @param address [String] p2pkh or p2sh address.
|
39
|
+
# @param value [Integer] issued amount.
|
40
|
+
def reissuable(script_pubkey, address, value)
|
41
|
+
color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
|
42
|
+
@issues[color_id] += value
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
# Issue reissuable token to the split outputs
|
47
|
+
# @param [Tapyrus::Script] script_pubkey The color id is generate from this script pubkey
|
48
|
+
# @param [String] address The address that is the token is sent to
|
49
|
+
# @param [Integer] value The issue amount of the token
|
50
|
+
# @param [Integer] split The number of the split outputs
|
51
|
+
def reissuable_split(script_pubkey, address, value, split)
|
52
|
+
split_value(value, split) do |value|
|
53
|
+
reissuable(script_pubkey, address, value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Issue non reissuable token
|
58
|
+
# @param out_point [Tapyrus::OutPoint] the out point at issue input.
|
59
|
+
# @param address [String] p2pkh or p2sh address.
|
60
|
+
# @param value [Integer] issued amount.
|
61
|
+
def non_reissuable(out_point, address, value)
|
62
|
+
color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
|
63
|
+
@issues[color_id] += value
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
# Issue non-reissuable token to the split outputs
|
68
|
+
# @param [Tapyrus::OutPoint] out_point The outpoint of the reissuable token
|
69
|
+
# @param [String] address The address that is the token is sent to
|
70
|
+
# @param [Integer] value The issue amount of the token
|
71
|
+
# @param [Integer] split The number of the split outputs
|
72
|
+
def non_reissuable_split(out_point, address, value, split)
|
73
|
+
split_value(value, split) do |value|
|
74
|
+
non_reissuable(out_point, address, value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Issue NFT
|
79
|
+
# @param out_point [Tapyrus::OutPoint] the out point at issue input.
|
80
|
+
# @param address [String] p2pkh or p2sh address.
|
81
|
+
# @raise [Glueby::ArgumentError] If the NFT is already issued in this contract builder.
|
82
|
+
def nft(out_point, address)
|
83
|
+
color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
|
84
|
+
raise Glueby::ArgumentError, 'NFT is already issued.' if @issues[color_id] == 1
|
85
|
+
@issues[color_id] = 1
|
86
|
+
super
|
87
|
+
end
|
88
|
+
|
89
|
+
# Burn token
|
90
|
+
# @param [Integer] value The burn amount of the token
|
91
|
+
# @param [Tapyrus::Color::ColorIdentifier] color_id The color id of the token
|
92
|
+
# @raise [Glueby::ArgumentError] If the color_id is default color id
|
93
|
+
def burn(value, color_id)
|
94
|
+
raise Glueby::ArgumentError, 'Burn TPC is not supported.' if color_id.default?
|
95
|
+
|
96
|
+
@burn_contract = true
|
97
|
+
@outgoings[color_id] ||= 0
|
98
|
+
@outgoings[color_id] += value
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add utxo to the transaction
|
103
|
+
# @param [Hash] utxo The utxo to add
|
104
|
+
# @option utxo [String] :txid The txid
|
105
|
+
# @option utxo [Integer] :vout The index of the output in the tx
|
106
|
+
# @option utxo [Integer] :amount The value of the output
|
107
|
+
# @option utxo [String] :script_pubkey The hex string of the script pubkey
|
108
|
+
# @option utxo [String] :color_id The color id hex string of the output
|
109
|
+
def add_utxo(utxo)
|
110
|
+
super(to_tapyrusrb_utxo_hash(utxo))
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# Add an UTXO which is sent to the address
|
115
|
+
# If the configuration is set to use UTXO provider, the UTXO is provided by the UTXO provider.
|
116
|
+
# Otherwise, the UTXO is provided by the wallet. In this case, the address parameter is ignored.
|
117
|
+
# This method creates and broadcasts a transaction that sends the amount to the address and add the UTXO
|
118
|
+
# to the transaction.
|
119
|
+
# @param [String] address The address that is the UTXO is sent to
|
120
|
+
# @param [Integer] amount The amount of the UTXO
|
121
|
+
# @param [Glueby::Internal::UtxoProvider] utxo_provider The UTXO provider
|
122
|
+
# @param [Boolean] only_finalized If true, the UTXO is provided from the finalized UTXO set. It is ignored if the configuration is set to use UTXO provider.
|
123
|
+
# @param [Glueby::Contract::FeeEstimator] fee_estimator It estimate fee for prev tx. It is ignored if the configuration is set to use UTXO provider.
|
124
|
+
def add_utxo_to!(
|
125
|
+
address:,
|
126
|
+
amount:,
|
127
|
+
utxo_provider: nil,
|
128
|
+
only_finalized: use_only_finalized_utxo,
|
129
|
+
fee_estimator: nil
|
130
|
+
)
|
131
|
+
tx, index = nil
|
132
|
+
|
133
|
+
if Glueby.configuration.use_utxo_provider? || utxo_provider
|
134
|
+
utxo_provider ||= UtxoProvider.instance
|
135
|
+
script_pubkey = Tapyrus::Script.parse_from_addr(address)
|
136
|
+
tx, index = utxo_provider.get_utxo(script_pubkey, amount)
|
137
|
+
else
|
138
|
+
fee_estimator ||= @fee_estimator
|
139
|
+
txb = Tapyrus::TxBuilder.new
|
140
|
+
fee = fee_estimator.fee(Contract::FeeEstimator.dummy_tx(txb.build))
|
141
|
+
_sum, utxos = sender_wallet
|
142
|
+
.collect_uncolored_outputs(fee + amount, nil, only_finalized)
|
143
|
+
utxos.each { |utxo| txb.add_utxo(to_tapyrusrb_utxo_hash(utxo)) }
|
144
|
+
tx = txb.pay(address, amount)
|
145
|
+
.change_address(sender_wallet.change_address)
|
146
|
+
.fee(fee)
|
147
|
+
.build
|
148
|
+
sender_wallet.sign_tx(tx)
|
149
|
+
index = 0
|
150
|
+
end
|
151
|
+
|
152
|
+
ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
|
153
|
+
# Here needs to use the return tx from Internal::Wallet#broadcast because the txid
|
154
|
+
# is changed if you enable FeeProvider.
|
155
|
+
tx = sender_wallet.broadcast(tx)
|
156
|
+
end
|
157
|
+
|
158
|
+
@prev_txs << tx
|
159
|
+
|
160
|
+
add_utxo({
|
161
|
+
script_pubkey: tx.outputs[index].script_pubkey.to_hex,
|
162
|
+
txid: tx.txid,
|
163
|
+
vout: index,
|
164
|
+
amount: tx.outputs[index].value
|
165
|
+
})
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
# Add an UTXO which is sent to the pay-to-contract address which is generated from the metadata
|
170
|
+
# @param [String] metadata The metadata of the pay-to-contract address
|
171
|
+
# @param [Integer] amount The amount of the UTXO
|
172
|
+
# @param [String] p2c_address The pay-to-contract address. You can use this parameter if you want to create an
|
173
|
+
# UTXO to before created address. It must be use with payment_base parameter.
|
174
|
+
# @param [String] payment_base The payment base of the pay-to-contract address. It should be compressed public
|
175
|
+
# key format. It must be use with p2c_address parameter.
|
176
|
+
# @param [Boolean] only_finalized If true, the UTXO is provided from the finalized UTXO set. It is ignored if the configuration is set to use UTXO provider.
|
177
|
+
# @param [Glueby::Contract::FeeEstimator] fee_estimator It estimate fee for prev tx. It is ignored if the configuration is set to use UTXO provider.
|
178
|
+
def add_p2c_utxo_to!(
|
179
|
+
metadata:,
|
180
|
+
amount:,
|
181
|
+
p2c_address: nil,
|
182
|
+
payment_base: nil,
|
183
|
+
only_finalized: use_only_finalized_utxo,
|
184
|
+
fee_estimator: nil
|
185
|
+
)
|
186
|
+
if p2c_address.nil? || payment_base.nil?
|
187
|
+
p2c_address, payment_base = sender_wallet
|
188
|
+
.create_pay_to_contract_address(metadata)
|
189
|
+
end
|
190
|
+
|
191
|
+
add_utxo_to!(
|
192
|
+
address: p2c_address,
|
193
|
+
amount: amount,
|
194
|
+
only_finalized: only_finalized,
|
195
|
+
fee_estimator: fee_estimator
|
196
|
+
)
|
197
|
+
@p2c_utxos << to_p2c_sign_tx_utxo_hash(@utxos.last)
|
198
|
+
.merge({
|
199
|
+
p2c_address: p2c_address,
|
200
|
+
payment_base: payment_base,
|
201
|
+
metadata: metadata
|
202
|
+
})
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
alias :original_build :build
|
207
|
+
def build
|
208
|
+
auto_fulfill_inputs_utxos_for_color if use_auto_fulfill_inputs
|
209
|
+
|
210
|
+
tx = Tapyrus::Tx.new
|
211
|
+
set_tpc_change_address
|
212
|
+
expand_input(tx)
|
213
|
+
@outputs.each { |output| tx.outputs << output }
|
214
|
+
add_change_for_colored_coin(tx)
|
215
|
+
|
216
|
+
|
217
|
+
change, provided_utxos = auto_fulfill_inputs_utxos_for_tpc(tx) if use_auto_fulfill_inputs
|
218
|
+
add_change_for_tpc(tx, change)
|
219
|
+
|
220
|
+
add_dummy_output(tx)
|
221
|
+
sign(tx, provided_utxos)
|
222
|
+
end
|
223
|
+
|
224
|
+
def change_address(address, color_id = Tapyrus::Color::ColorIdentifier.default)
|
225
|
+
if color_id.default?
|
226
|
+
super(address)
|
227
|
+
else
|
228
|
+
script_pubkey = Tapyrus::Script.parse_from_addr(address)
|
229
|
+
raise ArgumentError, 'invalid address' if !script_pubkey.p2pkh? && !script_pubkey.p2sh?
|
230
|
+
@change_script_pubkeys[color_id] = script_pubkey.add_color(color_id)
|
231
|
+
end
|
232
|
+
self
|
233
|
+
end
|
234
|
+
|
235
|
+
private :fee
|
236
|
+
|
237
|
+
def dummy_fee
|
238
|
+
fee_estimator.fee(Contract::FeeEstimator.dummy_tx(original_build))
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def estimated_fee
|
244
|
+
@fee ||= estimate_fee
|
245
|
+
end
|
246
|
+
|
247
|
+
def estimate_fee
|
248
|
+
tx = Tapyrus::Tx.new
|
249
|
+
expand_input(tx)
|
250
|
+
@outputs.each { |output| tx.outputs << output }
|
251
|
+
add_change_for_colored_coin(tx)
|
252
|
+
fee_estimator.fee(Contract::FeeEstimator.dummy_tx(tx))
|
253
|
+
end
|
254
|
+
|
255
|
+
def sign(tx, extra_utxos)
|
256
|
+
utxos = @utxos.map { |u| to_sign_tx_utxo_hash(u) } + (extra_utxos || [])
|
257
|
+
|
258
|
+
# Sign inputs from sender_wallet
|
259
|
+
tx = sender_wallet.sign_tx(tx, utxos)
|
260
|
+
|
261
|
+
# Sign inputs which is pay to contract output
|
262
|
+
@p2c_utxos.each do |utxo|
|
263
|
+
tx = sender_wallet
|
264
|
+
.sign_to_pay_to_contract_address(tx, utxo, utxo[:payment_base], utxo[:metadata])
|
265
|
+
end
|
266
|
+
|
267
|
+
# Sign inputs from UtxoProvider
|
268
|
+
Glueby::UtxoProvider.instance.wallet.sign_tx(tx, utxos) if Glueby.configuration.use_utxo_provider?
|
269
|
+
|
270
|
+
tx
|
271
|
+
end
|
272
|
+
|
273
|
+
def set_tpc_change_address
|
274
|
+
if Glueby.configuration.use_utxo_provider?
|
275
|
+
change_address(UtxoProvider.instance.wallet.change_address)
|
276
|
+
else
|
277
|
+
change_address(@sender_wallet.change_address)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def add_change_for_colored_coin(tx)
|
282
|
+
@incomings.each do |color_id, in_amount|
|
283
|
+
next if color_id.default?
|
284
|
+
|
285
|
+
out_amount = @outgoings[color_id] || 0
|
286
|
+
change = in_amount - out_amount
|
287
|
+
next if change <= 0
|
288
|
+
|
289
|
+
unless @change_script_pubkeys[color_id]
|
290
|
+
raise Glueby::ArgumentError, "The change address for color_id #{color_id.to_hex} must be set."
|
291
|
+
end
|
292
|
+
tx.outputs << Tapyrus::TxOut.new(script_pubkey: @change_script_pubkeys[color_id], value: change)
|
293
|
+
end
|
294
|
+
tx
|
295
|
+
end
|
296
|
+
|
297
|
+
def add_change_for_tpc(tx, change = nil)
|
298
|
+
raise Glueby::ArgumentError, "The change address for TPC must be set." unless @change_script_pubkey
|
299
|
+
change ||= begin
|
300
|
+
in_amount = @incomings[Tapyrus::Color::ColorIdentifier.default] || 0
|
301
|
+
out_amount = @outgoings[Tapyrus::Color::ColorIdentifier.default] || 0
|
302
|
+
|
303
|
+
in_amount - out_amount - estimated_fee
|
304
|
+
end
|
305
|
+
|
306
|
+
raise Contract::Errors::InsufficientFunds if change < 0
|
307
|
+
|
308
|
+
change_output = Tapyrus::TxOut.new(script_pubkey: @change_script_pubkey, value: change)
|
309
|
+
return tx if change_output.dust?
|
310
|
+
|
311
|
+
tx.outputs << change_output
|
312
|
+
|
313
|
+
tx
|
314
|
+
end
|
315
|
+
|
316
|
+
# @return [Integer] The TPC change amount
|
317
|
+
# @return [Array<Hash>] The provided UTXOs
|
318
|
+
def auto_fulfill_inputs_utxos_for_tpc(tx)
|
319
|
+
target_amount = @outgoings[Tapyrus::Color::ColorIdentifier.default] || 0
|
320
|
+
|
321
|
+
provider = if Glueby.configuration.use_utxo_provider?
|
322
|
+
UtxoProvider.instance
|
323
|
+
else
|
324
|
+
sender_wallet
|
325
|
+
end
|
326
|
+
|
327
|
+
_tx, fee, tpc_amount, provided_utxos = provider.fill_uncolored_inputs(
|
328
|
+
tx,
|
329
|
+
target_amount: target_amount,
|
330
|
+
current_amount: @incomings[Tapyrus::Color::ColorIdentifier.default] || 0,
|
331
|
+
fee_estimator: fee_estimator
|
332
|
+
)
|
333
|
+
|
334
|
+
change = tpc_amount - target_amount - fee
|
335
|
+
[change, provided_utxos]
|
336
|
+
end
|
337
|
+
|
338
|
+
def auto_fulfill_inputs_utxos_for_color
|
339
|
+
# fulfill colored inputs
|
340
|
+
@outgoings.each do |color_id, outgoing_amount|
|
341
|
+
next if color_id.default?
|
342
|
+
|
343
|
+
target_amount = outgoing_amount - (@incomings[color_id] || 0) - @issues[color_id]
|
344
|
+
next if target_amount <= 0
|
345
|
+
|
346
|
+
@sender_wallet.collect_colored_outputs(
|
347
|
+
color_id,
|
348
|
+
target_amount,
|
349
|
+
nil,
|
350
|
+
use_only_finalized_utxo,
|
351
|
+
true
|
352
|
+
)[1]
|
353
|
+
.each { |utxo| add_utxo(utxo) }
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
|
358
|
+
def get_fee_estimator(fee_estimator_name)
|
359
|
+
Glueby::Contract::FeeEstimator.get_const("#{fee_estimator_name.capitalize}", false).new
|
360
|
+
end
|
361
|
+
|
362
|
+
def valid_fee_estimator?(fee_estimator)
|
363
|
+
[:fixed, :auto].include?(fee_estimator)
|
364
|
+
end
|
365
|
+
|
366
|
+
def use_only_finalized_utxo
|
367
|
+
!use_unfinalized_utxo
|
368
|
+
end
|
369
|
+
|
370
|
+
# Set fee estimator
|
371
|
+
# @param [Symbol|Glueby::Contract::FeeEstimator] fee_estimator :auto or :fixed
|
372
|
+
def set_fee_estimator(fee_estimator)
|
373
|
+
if fee_estimator.is_a?(Symbol)
|
374
|
+
raise Glueby::ArgumentError, 'fee_estiamtor can be :fixed or :auto' unless valid_fee_estimator?(fee_estimator)
|
375
|
+
@fee_estimator = get_fee_estimator(fee_estimator)
|
376
|
+
else
|
377
|
+
@fee_estimator = fee_estimator
|
378
|
+
end
|
379
|
+
self
|
380
|
+
end
|
381
|
+
|
382
|
+
# Split the value into the number of split. The last value is added the remainder of the division.
|
383
|
+
# It call the block with the value of each split.
|
384
|
+
def split_value(value, split)
|
385
|
+
if value < split
|
386
|
+
split = value
|
387
|
+
split_value = 1
|
388
|
+
else
|
389
|
+
split_value = (value / split).to_i
|
390
|
+
end
|
391
|
+
(split - 1).times { yield(split_value) }
|
392
|
+
yield(value - split_value * (split - 1))
|
393
|
+
self
|
394
|
+
end
|
395
|
+
|
396
|
+
# If the tx has no output due to the contract creating a burn token, a dummy output should be added to make
|
397
|
+
# the transaction valid.
|
398
|
+
def add_dummy_output(tx)
|
399
|
+
if @burn_contract && tx.outputs.size == 0
|
400
|
+
|
401
|
+
tx.outputs << Tapyrus::TxOut.new(
|
402
|
+
value: 0,
|
403
|
+
script_pubkey: Tapyrus::Script.new << Tapyrus::Opcodes::OP_RETURN
|
404
|
+
)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# The UTXO format that is used in Tapyrus::TxBuilder
|
409
|
+
# @param utxo
|
410
|
+
# @option utxo [String] :txid The txid
|
411
|
+
# @option utxo [Integer] :vout The index of the output in the tx
|
412
|
+
# @option utxo [Integer] :amount The value of the output
|
413
|
+
# @option utxo [String] :script_pubkey The hex string of the script pubkey
|
414
|
+
# @option utxo [String] :color_id The hex string of the color id
|
415
|
+
def to_tapyrusrb_utxo_hash(utxo)
|
416
|
+
color_id = if utxo[:color_id]
|
417
|
+
Tapyrus::Color::ColorIdentifier.parse_from_payload(utxo[:color_id].htb)
|
418
|
+
else
|
419
|
+
Tapyrus::Color::ColorIdentifier.default
|
420
|
+
end
|
421
|
+
{
|
422
|
+
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
423
|
+
txid: utxo[:txid],
|
424
|
+
index: utxo[:vout],
|
425
|
+
value: utxo[:amount],
|
426
|
+
color_id: color_id
|
427
|
+
}
|
428
|
+
end
|
429
|
+
|
430
|
+
# The UTXO format that is used in AbstractWalletAdapter#sign_tx
|
431
|
+
# @param utxo The return value of #to_tapyrusrb_utxo_hash
|
432
|
+
# @option utxo [String] :txid The txid
|
433
|
+
# @option utxo [Integer] :index The index of the output in the tx
|
434
|
+
# @option utxo [Integer] :amount The value of the output
|
435
|
+
# @option utxo [String] :script_pubkey The hex string of the script pubkey
|
436
|
+
def to_sign_tx_utxo_hash(utxo)
|
437
|
+
{
|
438
|
+
scriptPubKey: utxo[:script_pubkey].to_hex,
|
439
|
+
txid: utxo[:txid],
|
440
|
+
vout: utxo[:index],
|
441
|
+
amount: utxo[:value]
|
442
|
+
}
|
443
|
+
end
|
444
|
+
|
445
|
+
# The UTXO format that is used in AbstractWalletAdapter#sign_to_pay_to_contract_address
|
446
|
+
# @param utxo The return value of #to_tapyrusrb_utxo_hash
|
447
|
+
# @option utxo [String] :txid The txid
|
448
|
+
# @option utxo [Integer] :index The index of the output in the tx
|
449
|
+
# @option utxo [Integer] :amount The value of the output
|
450
|
+
# @option utxo [String] :script_pubkey The hex string of the script pubkey
|
451
|
+
def to_p2c_sign_tx_utxo_hash(utxo)
|
452
|
+
{
|
453
|
+
script_pubkey: utxo[:script_pubkey].to_hex,
|
454
|
+
txid: utxo[:txid],
|
455
|
+
vout: utxo[:index],
|
456
|
+
amount: utxo[:value]
|
457
|
+
}
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
@@ -82,6 +82,8 @@ module Glueby
|
|
82
82
|
# - vout: [Integer] Output index
|
83
83
|
# - amount: [Integer] Amount of the UTXO as tapyrus unit
|
84
84
|
# - finalized: [Boolean] Whether the UTXO is finalized
|
85
|
+
# - color_id: [String] Color id of the UTXO. If it is TPC UTXO, color_id is nil.
|
86
|
+
# - script_pubkey: [String] Script pubkey of the UTXO
|
85
87
|
def list_unspent(wallet_id, only_finalized = true, label = nil)
|
86
88
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
87
89
|
end
|
@@ -49,8 +49,9 @@ module Glueby
|
|
49
49
|
private
|
50
50
|
|
51
51
|
def check_dust_output
|
52
|
-
|
53
|
-
|
52
|
+
output = Tapyrus::TxOut.new(value: value, script_pubkey: Tapyrus::Script.parse_from_payload(script_pubkey.htb))
|
53
|
+
if !color_id && output.dust?
|
54
|
+
errors.add(:value, "is less than dust limit(#{output.send(:dust_threshold)})")
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -146,20 +146,74 @@ module Glueby
|
|
146
146
|
wallet_adapter.create_pubkey(id)
|
147
147
|
end
|
148
148
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
149
|
+
# Collect TPC UTXOs up to the specified amount.
|
150
|
+
# @param [Integer] amount The amount of colored token to collect. If the amount is nil, return all TPC UTXOs
|
151
|
+
# @param [String] label The label of UTXO to collect
|
152
|
+
# @param [Boolean] only_finalized The flag to collect only finalized UTXO
|
153
|
+
# @param [Boolean] shuffle The flag to shuffle UTXO before collecting
|
154
|
+
# @param [Boolean] lock_utxos The flag to lock returning UTXOs to prevent to be used from other threads or processes
|
155
|
+
# @param [Array<Hash] excludes The UTXO list to exclude the method result. Each hash must hub keys that are :txid and :vout
|
156
|
+
# @return [Integer] The sum of return UTXOs
|
157
|
+
# @return [Array<Hash>] An array of UTXO hash
|
158
|
+
#
|
159
|
+
# ## The UTXO structure
|
160
|
+
#
|
161
|
+
# - txid: [String] Transaction id
|
162
|
+
# - vout: [Integer] Output index
|
163
|
+
# - amount: [Integer] Amount of the UTXO as tapyrus unit
|
164
|
+
# - finalized: [Boolean] Whether the UTXO is finalized
|
165
|
+
# - script_pubkey: [String] ScriptPubkey of the UTXO
|
166
|
+
def collect_uncolored_outputs(
|
167
|
+
amount = nil,
|
168
|
+
label = nil,
|
169
|
+
only_finalized = true,
|
170
|
+
shuffle = false,
|
171
|
+
lock_utxos = false,
|
172
|
+
excludes = []
|
173
|
+
)
|
174
|
+
collect_utxos(amount, label, only_finalized, shuffle, lock_utxos, excludes) do |output|
|
175
|
+
next false unless output[:color_id].nil?
|
176
|
+
next yield(output) if block_given?
|
177
|
+
|
178
|
+
true
|
179
|
+
end
|
180
|
+
end
|
159
181
|
|
160
|
-
|
182
|
+
# Collect colored coin UTXOs up to the specified amount.
|
183
|
+
# @param [Tapyrus::Color::ColorIdentifier] color_id The color identifier of colored token
|
184
|
+
# @param [Integer] amount The amount of colored token to collect. If the amount is nil, return all UTXOs with color_id
|
185
|
+
# @param [String] label The label of UTXO to collect
|
186
|
+
# @param [Boolean] only_finalized The flag to collect only finalized UTXO
|
187
|
+
# @param [Boolean] shuffle The flag to shuffle UTXO before collecting
|
188
|
+
# @param [Boolean] lock_utxos The flag to lock returning UTXOs to prevent to be used from other threads or processes
|
189
|
+
# @param [Array<Hash] excludes The UTXO list to exclude the method result. Each hash must hub keys that are :txid and :vout
|
190
|
+
# @return [Integer] The sum of return UTXOs
|
191
|
+
# @return [Array<Hash>] An array of UTXO hash
|
192
|
+
#
|
193
|
+
# ## The UTXO structure
|
194
|
+
#
|
195
|
+
# - txid: [String] Transaction id
|
196
|
+
# - vout: [Integer] Output index
|
197
|
+
# - amount: [Integer] Amount of the UTXO as tapyrus unit
|
198
|
+
# - finalized: [Boolean] Whether the UTXO is finalized
|
199
|
+
# - script_pubkey: [String] ScriptPubkey of the UTXO
|
200
|
+
def collect_colored_outputs(
|
201
|
+
color_id,
|
202
|
+
amount = nil,
|
203
|
+
label = nil,
|
204
|
+
only_finalized = true,
|
205
|
+
shuffle = false,
|
206
|
+
lock_utxos = false,
|
207
|
+
excludes = []
|
208
|
+
)
|
209
|
+
collect_utxos(amount, label, only_finalized, shuffle, lock_utxos, excludes) do |output|
|
210
|
+
next false unless output[:color_id] == color_id.to_hex
|
211
|
+
next yield(output) if block_given?
|
212
|
+
|
213
|
+
true
|
161
214
|
end
|
162
|
-
|
215
|
+
rescue Glueby::Contract::Errors::InsufficientFunds
|
216
|
+
raise Glueby::Contract::Errors::InsufficientTokens
|
163
217
|
end
|
164
218
|
|
165
219
|
def get_addresses(label = nil)
|
@@ -182,11 +236,94 @@ module Glueby
|
|
182
236
|
wallet_adapter.has_address?(id, address)
|
183
237
|
end
|
184
238
|
|
239
|
+
# Fill inputs in the tx up to target_amount of TPC from UTXOs in the wallet.
|
240
|
+
# This method should be called before adding change output and script_sig in outputs.
|
241
|
+
# FeeEstimator.dummy_tx returns fee amount to the TX that will be added one TPC input, a change TPC output and
|
242
|
+
# script sigs in outputs.
|
243
|
+
# @param [Tapyrus::Tx] tx The tx that will be filled the inputs
|
244
|
+
# @param [Integer] target_amount The tapyrus amount the tx is expected to be added in this method
|
245
|
+
# @param [Integer] current_amount The tapyrus amount the tx already has in its inputs
|
246
|
+
# @param [Glueby::Contract::FeeEstimator] fee_estimator
|
247
|
+
# @return [Tapyrus::Tx] tx The tx that is added inputs
|
248
|
+
# @return [Integer] fee The final fee after the inputs are filled
|
249
|
+
# @return [Integer] current_amount The final amount of the tx inputs
|
250
|
+
# @return [Array<Hash>] provided_utxos The utxos that are added to the tx inputs
|
251
|
+
def fill_uncolored_inputs(
|
252
|
+
tx,
|
253
|
+
target_amount: ,
|
254
|
+
current_amount: 0,
|
255
|
+
fee_estimator: Contract::FeeEstimator::Fixed.new,
|
256
|
+
&block
|
257
|
+
)
|
258
|
+
fee = fee_estimator.fee(Contract::FeeEstimator.dummy_tx(tx, dummy_input_count: 0))
|
259
|
+
provided_utxos = []
|
260
|
+
|
261
|
+
while current_amount - fee < target_amount
|
262
|
+
sum, utxos = collect_uncolored_outputs(
|
263
|
+
fee + target_amount - current_amount,
|
264
|
+
nil, nil, true, true,
|
265
|
+
provided_utxos,
|
266
|
+
&block
|
267
|
+
)
|
268
|
+
|
269
|
+
utxos.each do |utxo|
|
270
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: Tapyrus::OutPoint.from_txid(utxo[:txid], utxo[:vout]))
|
271
|
+
provided_utxos << utxo
|
272
|
+
end
|
273
|
+
current_amount += sum
|
274
|
+
|
275
|
+
new_fee = fee_estimator.fee(Contract::FeeEstimator.dummy_tx(tx, dummy_input_count: 0))
|
276
|
+
fee = new_fee
|
277
|
+
end
|
278
|
+
|
279
|
+
[tx, fee, current_amount, provided_utxos]
|
280
|
+
end
|
281
|
+
|
185
282
|
private
|
186
283
|
|
187
284
|
def wallet_adapter
|
188
285
|
self.class.wallet_adapter
|
189
286
|
end
|
287
|
+
|
288
|
+
def collect_utxos(
|
289
|
+
amount,
|
290
|
+
label,
|
291
|
+
only_finalized,
|
292
|
+
shuffle = true,
|
293
|
+
lock_utxos = false,
|
294
|
+
excludes = []
|
295
|
+
)
|
296
|
+
collect_all = amount.nil?
|
297
|
+
|
298
|
+
raise Glueby::ArgumentError, 'amount must be positive' unless collect_all || amount.positive?
|
299
|
+
utxos = list_unspent(only_finalized, label)
|
300
|
+
utxos = utxos.shuffle if shuffle
|
301
|
+
|
302
|
+
r = utxos.inject([0, []]) do |(sum, outputs), output|
|
303
|
+
if excludes.is_a?(Array) &&
|
304
|
+
!excludes.empty? &&
|
305
|
+
excludes.find { |i| i[:txid] == output[:txid] && i[:vout] == output[:vout] }
|
306
|
+
next [sum, outputs]
|
307
|
+
end
|
308
|
+
|
309
|
+
if block_given?
|
310
|
+
next [sum, outputs] unless yield(output)
|
311
|
+
end
|
312
|
+
|
313
|
+
if lock_utxos
|
314
|
+
next [sum, outputs] unless lock_unspent(output)
|
315
|
+
end
|
316
|
+
|
317
|
+
sum += output[:amount]
|
318
|
+
outputs << output
|
319
|
+
return [sum, outputs] unless collect_all || sum < amount
|
320
|
+
|
321
|
+
[sum, outputs]
|
322
|
+
end
|
323
|
+
raise Glueby::Contract::Errors::InsufficientFunds unless collect_all
|
324
|
+
|
325
|
+
r
|
326
|
+
end
|
190
327
|
end
|
191
328
|
end
|
192
329
|
end
|
data/lib/glueby/internal.rb
CHANGED