glueby 0.4.3 → 0.5.2
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/README.md +90 -0
- data/lib/glueby/configuration.rb +32 -2
- data/lib/glueby/contract/fee_estimator.rb +5 -1
- data/lib/glueby/contract/timestamp.rb +43 -25
- data/lib/glueby/contract/token.rb +34 -8
- data/lib/glueby/contract/tx_builder.rb +97 -30
- data/lib/glueby/fee_provider/tasks.rb +1 -1
- data/lib/glueby/fee_provider.rb +11 -0
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +5 -1
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +13 -4
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +17 -9
- data/lib/glueby/internal/wallet.rb +7 -8
- data/lib/glueby/railtie.rb +1 -0
- data/lib/glueby/utxo_provider/tasks.rb +135 -0
- data/lib/glueby/utxo_provider.rb +85 -0
- data/lib/glueby/version.rb +1 -1
- data/lib/glueby.rb +1 -0
- data/lib/tasks/glueby/contract/timestamp.rake +10 -3
- data/lib/tasks/glueby/fee_provider.rake +1 -1
- data/lib/tasks/glueby/utxo_provider.rake +18 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70bc38595142acecb4510e2bae12054de9bf4d8da4ddacbe220be0dab7c76324
|
4
|
+
data.tar.gz: 87f1bc6a29b3aa38a35aa929c1cf8cc5190ec768265b873cdebc7c4b47481996
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c87104af19662964d241fbd132fa351b3919c7c4c3079fbb3a0b1f1a2275b24c400d07edc570f63875b3f5884a18bae2720519377ea17e6bda38bfd1717d0702
|
7
|
+
data.tar.gz: aeae4f6edae4081df4288ee2ac35188abefefd8e240b2536447890de547f5b4136ed95caf8142fd4dc85b57947253221d641fde1c0fcd8a8fae1d22ae09ae420
|
data/README.md
CHANGED
@@ -27,6 +27,10 @@ Glueby has below features.
|
|
27
27
|
FeeProvider module can bear payments of sender's fees. You should provide funds for fees to FeeProvider before use.
|
28
28
|
See how to set up at [Use fee provider mode](#use-fee-provider-mode)
|
29
29
|
|
30
|
+
5. Utxo Provider
|
31
|
+
The UtxoProvider allows users to create a variety of transactions without having to manage the TPCs they hold in their wallets.
|
32
|
+
See more details at [Use utxo provider](#use-utxo-provider)
|
33
|
+
|
30
34
|
## Installation
|
31
35
|
|
32
36
|
Add this line to your application's Gemfile:
|
@@ -296,6 +300,7 @@ Glueby.configure do |config|
|
|
296
300
|
# The fee that Fee Provider pays on each transaction.
|
297
301
|
fixed_fee: 1000,
|
298
302
|
# Fee Provider tries to keep the number of utxo in utxo pool as this size using `glueby:fee_provider:manage_utxo_pool` rake task
|
303
|
+
# This size should not be greater than 2000.
|
299
304
|
utxo_pool_size: 20
|
300
305
|
}
|
301
306
|
end
|
@@ -368,6 +373,91 @@ Configuration:
|
|
368
373
|
utxo_pool_size = 20
|
369
374
|
```
|
370
375
|
|
376
|
+
## Use Utxo Provider
|
377
|
+
|
378
|
+
UtxoProvider will pay TPC on behalf of the user.
|
379
|
+
|
380
|
+
TPCs are required to create transactions in many cases where Glueby is used, such as issuing tokens or recording timestamps.
|
381
|
+
However, on the other hand, each user may not want to fund or manage TPCs.
|
382
|
+
|
383
|
+
The UtxoProvider allows users to create a variety of transactions without having to manage the TPCs they hold in their wallets.
|
384
|
+
|
385
|
+
### Set up Utxo Provider
|
386
|
+
|
387
|
+
1. Configure using Glueby.configure
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
Glueby.configure do |config|
|
391
|
+
# using Utxo Provider
|
392
|
+
config.enable_utxo_provider!
|
393
|
+
|
394
|
+
# If not using Utxo Provider and each wallet manages TPCs by itself (Default behavior)
|
395
|
+
# config.disable_utxo_provider!
|
396
|
+
|
397
|
+
config.utxo_provider_config = {
|
398
|
+
# The amount that each utxo in utxo pool posses.
|
399
|
+
default_value: 1_000,
|
400
|
+
# The number of utxos in utxo pool. This size should not be greater than 2000.
|
401
|
+
utxo_pool_size: 20
|
402
|
+
}
|
403
|
+
end
|
404
|
+
```
|
405
|
+
|
406
|
+
2. Deposit TPC into Utxo Provider's wallet
|
407
|
+
|
408
|
+
Get an address from the wallet, and send enough TPCs to the address.
|
409
|
+
|
410
|
+
```
|
411
|
+
$ bundle exec rake glueby:utxo_provider:address
|
412
|
+
mqYTLdLCUCCZkTkcpbVx1GqpvV1gK4euRD
|
413
|
+
```
|
414
|
+
|
415
|
+
3. Manage UTXO pool
|
416
|
+
|
417
|
+
Run the rake task `glueby:utxo_provider:manage_utxo_pool`
|
418
|
+
This rake task tries to split UTOXs up to `utxo_pool_size`. If the pool has more than `utxo_pool_size` UTXOs, it does nothing
|
419
|
+
|
420
|
+
```
|
421
|
+
$ bundle exec rake glueby:utxo_provider:manage_utxo_pool
|
422
|
+
|
423
|
+
Status: Ready
|
424
|
+
TPC amount: 4_999_990_000
|
425
|
+
UTXO pool size: 20
|
426
|
+
|
427
|
+
Configuration:
|
428
|
+
default_value = 1_000
|
429
|
+
utxo_pool_size = 20
|
430
|
+
```
|
431
|
+
|
432
|
+
If you want to get the status information, you can use the `status` task.
|
433
|
+
|
434
|
+
```
|
435
|
+
$ bundle exec rake glueby:utxo_provider:status
|
436
|
+
Status: Ready q
|
437
|
+
TPC amount: 4_999_990_000
|
438
|
+
UTXO pool size: 20
|
439
|
+
|
440
|
+
Configuration:
|
441
|
+
default_value = 1_000
|
442
|
+
utxo_pool_size = 20
|
443
|
+
|
444
|
+
```
|
445
|
+
|
446
|
+
## Other configurations
|
447
|
+
|
448
|
+
### Default fixed fee for the FixedFeeEstimator
|
449
|
+
|
450
|
+
The architecture of Glueby accepts any fee estimation strategies for paying transactions fee. However, we officially support only one strategy: the fixed fee strategy.
|
451
|
+
It just returns a fixed fee value without any estimation.
|
452
|
+
Here provides a configuration to modify the default fixed fee value it returns like this:
|
453
|
+
|
454
|
+
```ruby
|
455
|
+
Glueby.configure do |config|
|
456
|
+
config.default_fixed_fee = 10_000
|
457
|
+
end
|
458
|
+
```
|
459
|
+
|
460
|
+
|
371
461
|
## Development
|
372
462
|
|
373
463
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/glueby/configuration.rb
CHANGED
@@ -10,11 +10,17 @@ module Glueby
|
|
10
10
|
# end
|
11
11
|
class Configuration
|
12
12
|
|
13
|
-
attr_reader :fee_provider_bears
|
13
|
+
attr_reader :fee_provider_bears, :use_utxo_provider
|
14
14
|
alias_method :fee_provider_bears?, :fee_provider_bears
|
15
|
+
alias_method :use_utxo_provider?, :use_utxo_provider
|
16
|
+
|
17
|
+
module Errors
|
18
|
+
class InvalidConfiguration < StandardError; end
|
19
|
+
end
|
15
20
|
|
16
21
|
def initialize
|
17
22
|
@fee_provider_bears = false
|
23
|
+
@use_utxo_provider = false
|
18
24
|
end
|
19
25
|
|
20
26
|
# Specify wallet adapter.
|
@@ -54,9 +60,33 @@ module Glueby
|
|
54
60
|
# Specify FeeProvider configuration.
|
55
61
|
# @param [Hash] config
|
56
62
|
# @option config [Integer] :fixed_fee - The fee that Fee Provider pays on each transaction.
|
57
|
-
# @option config [Integer] :utxo_pool_size - Fee Provider tries to keep the number of utxo in utxo pool as this size using `glueby:fee_provider:manage_utxo_pool` rake task
|
63
|
+
# @option config [Integer] :utxo_pool_size - Fee Provider tries to keep the number of utxo in utxo pool as this size using `glueby:fee_provider:manage_utxo_pool` rake task. this size should not be greater than 2000.
|
58
64
|
def fee_provider_config=(config)
|
59
65
|
FeeProvider.configure(config)
|
60
66
|
end
|
67
|
+
|
68
|
+
# Enable UtxoProvider feature
|
69
|
+
def enable_utxo_provider!
|
70
|
+
@use_utxo_provider = true
|
71
|
+
end
|
72
|
+
|
73
|
+
# Disable UtxoProvider feature
|
74
|
+
def disable_utxo_provider!
|
75
|
+
@use_utxo_provider = false
|
76
|
+
end
|
77
|
+
|
78
|
+
# Set UtxoProvider configuration
|
79
|
+
# @param [Hash] config
|
80
|
+
# @option config [Integer] :default_value - The fee that Fee Provider pays on each transaction.
|
81
|
+
# @option config [Integer] :utxo_pool_size - Utxo Provider tries to keep the number of utxo in utxo pool as this size using `glueby:utxo_provider:manage_utxo_pool` rake task. this size should not be greater than 2000.
|
82
|
+
def utxo_provider_config=(config)
|
83
|
+
UtxoProvider.configure(config)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Set default fixed fee to the FixedFeeEstimator
|
87
|
+
# @param [Integer] fee The default fee value in tapyrus to the FixedFeeEstimator
|
88
|
+
def default_fixed_fee=(fee)
|
89
|
+
Contract::FixedFeeEstimator.default_fixed_fee = fee
|
90
|
+
end
|
61
91
|
end
|
62
92
|
end
|
@@ -22,7 +22,11 @@ module Glueby
|
|
22
22
|
class FixedFeeEstimator
|
23
23
|
include FeeEstimator
|
24
24
|
|
25
|
-
|
25
|
+
class << self
|
26
|
+
attr_accessor :default_fixed_fee
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(fixed_fee: FixedFeeEstimator.default_fixed_fee || 10_000)
|
26
30
|
@fixed_fee = fixed_fee
|
27
31
|
end
|
28
32
|
|
@@ -16,30 +16,45 @@ module Glueby
|
|
16
16
|
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
17
17
|
module_function
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
19
|
+
def create_txs(wallet, prefix, data, fee_estimator, utxo_provider)
|
20
|
+
txb = Tapyrus::TxBuilder.new
|
21
|
+
txb.data(prefix + data)
|
22
|
+
fee = fee_estimator.fee(dummy_tx(txb.build))
|
23
|
+
if utxo_provider
|
24
|
+
script_pubkey = Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
|
25
|
+
funding_tx, index = utxo_provider.get_utxo(script_pubkey, fee)
|
26
|
+
txb.add_utxo({
|
27
|
+
script_pubkey: funding_tx.outputs[index].script_pubkey,
|
28
|
+
txid: funding_tx.txid,
|
29
|
+
index: index,
|
30
|
+
value: funding_tx.outputs[index].value
|
31
|
+
})
|
32
|
+
else
|
33
|
+
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
|
34
|
+
outputs.each do |utxo|
|
35
|
+
txb.add_utxo({
|
36
|
+
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
37
|
+
txid: utxo[:txid],
|
38
|
+
index: utxo[:vout],
|
39
|
+
value: utxo[:amount]
|
40
|
+
})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
prev_txs = if funding_tx
|
45
|
+
output = funding_tx.outputs.first
|
46
|
+
[{
|
47
|
+
txid: funding_tx.txid,
|
48
|
+
vout: 0,
|
49
|
+
scriptPubKey: output.script_pubkey.to_hex,
|
50
|
+
amount: output.value
|
51
|
+
}]
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
37
55
|
|
38
|
-
|
39
|
-
|
40
|
-
script << Tapyrus::Script::OP_RETURN
|
41
|
-
script << create_payload(prefix, data)
|
42
|
-
script
|
56
|
+
txb.fee(fee).change_address(wallet.internal_wallet.change_address)
|
57
|
+
[funding_tx, wallet.internal_wallet.sign_tx(txb.build, prev_txs)]
|
43
58
|
end
|
44
59
|
|
45
60
|
def get_transaction(tx)
|
@@ -63,13 +78,15 @@ module Glueby
|
|
63
78
|
content:,
|
64
79
|
prefix: '',
|
65
80
|
fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
|
66
|
-
digest: :sha256
|
81
|
+
digest: :sha256,
|
82
|
+
utxo_provider: nil
|
67
83
|
)
|
68
84
|
@wallet = wallet
|
69
85
|
@content = content
|
70
86
|
@prefix = prefix
|
71
87
|
@fee_estimator = fee_estimator
|
72
88
|
@digest = digest
|
89
|
+
@utxo_provider = utxo_provider
|
73
90
|
end
|
74
91
|
|
75
92
|
# broadcast to Tapyrus Core
|
@@ -79,7 +96,8 @@ module Glueby
|
|
79
96
|
def save!
|
80
97
|
raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
|
81
98
|
|
82
|
-
@tx =
|
99
|
+
funding_tx, @tx = create_txs(@wallet, @prefix, digest_content, @fee_estimator, @utxo_provider)
|
100
|
+
@wallet.internal_wallet.broadcast(funding_tx) if funding_tx
|
83
101
|
@txid = @wallet.internal_wallet.broadcast(@tx)
|
84
102
|
end
|
85
103
|
|
@@ -80,7 +80,8 @@ module Glueby
|
|
80
80
|
private
|
81
81
|
|
82
82
|
def issue_reissuable_token(issuer:, amount:)
|
83
|
-
|
83
|
+
utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
|
84
|
+
funding_tx = create_funding_tx(wallet: issuer, utxo_provider: utxo_provider)
|
84
85
|
script_pubkey = funding_tx.outputs.first.script_pubkey
|
85
86
|
color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
|
86
87
|
|
@@ -96,21 +97,37 @@ module Glueby
|
|
96
97
|
end
|
97
98
|
|
98
99
|
def issue_non_reissuable_token(issuer:, amount:)
|
99
|
-
|
100
|
+
utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
|
101
|
+
funding_tx = create_funding_tx(wallet: issuer, utxo_provider: utxo_provider) if utxo_provider
|
102
|
+
funding_tx = issuer.internal_wallet.broadcast(funding_tx) if funding_tx
|
103
|
+
|
104
|
+
tx = create_issue_tx_for_non_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
|
100
105
|
tx = issuer.internal_wallet.broadcast(tx)
|
101
106
|
|
102
107
|
out_point = tx.inputs.first.out_point
|
103
108
|
color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
|
104
|
-
|
109
|
+
if funding_tx
|
110
|
+
[[funding_tx, tx], color_id]
|
111
|
+
else
|
112
|
+
[[tx], color_id]
|
113
|
+
end
|
105
114
|
end
|
106
115
|
|
107
116
|
def issue_nft_token(issuer:)
|
108
|
-
|
117
|
+
utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
|
118
|
+
funding_tx = create_funding_tx(wallet: issuer, utxo_provider: utxo_provider) if utxo_provider
|
119
|
+
funding_tx = issuer.internal_wallet.broadcast(funding_tx) if funding_tx
|
120
|
+
|
121
|
+
tx = create_issue_tx_for_nft_token(funding_tx: funding_tx, issuer: issuer)
|
109
122
|
tx = issuer.internal_wallet.broadcast(tx)
|
110
123
|
|
111
124
|
out_point = tx.inputs.first.out_point
|
112
125
|
color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
|
113
|
-
|
126
|
+
if funding_tx
|
127
|
+
[[funding_tx, tx], color_id]
|
128
|
+
else
|
129
|
+
[[tx], color_id]
|
130
|
+
end
|
114
131
|
end
|
115
132
|
end
|
116
133
|
|
@@ -128,9 +145,10 @@ module Glueby
|
|
128
145
|
def reissue!(issuer:, amount:)
|
129
146
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
130
147
|
raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
|
148
|
+
utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
|
131
149
|
|
132
150
|
if validate_reissuer(wallet: issuer)
|
133
|
-
funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey)
|
151
|
+
funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey, utxo_provider: utxo_provider)
|
134
152
|
funding_tx = issuer.internal_wallet.broadcast(funding_tx)
|
135
153
|
tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
|
136
154
|
tx = issuer.internal_wallet.broadcast(tx)
|
@@ -153,7 +171,11 @@ module Glueby
|
|
153
171
|
def transfer!(sender:, receiver_address:, amount: 1)
|
154
172
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
155
173
|
|
156
|
-
|
174
|
+
utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
|
175
|
+
funding_tx = create_funding_tx(wallet: sender, utxo_provider: utxo_provider) if utxo_provider
|
176
|
+
funding_tx = sender.internal_wallet.broadcast(funding_tx) if funding_tx
|
177
|
+
|
178
|
+
tx = create_transfer_tx(funding_tx: funding_tx, color_id: color_id, sender: sender, receiver_address: receiver_address, amount: amount)
|
157
179
|
sender.internal_wallet.broadcast(tx)
|
158
180
|
[color_id, tx]
|
159
181
|
end
|
@@ -169,7 +191,11 @@ module Glueby
|
|
169
191
|
def burn!(sender:, amount: 0)
|
170
192
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
171
193
|
|
172
|
-
|
194
|
+
utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
|
195
|
+
funding_tx = create_funding_tx(wallet: sender, utxo_provider: utxo_provider) if utxo_provider
|
196
|
+
funding_tx = sender.internal_wallet.broadcast(funding_tx) if funding_tx
|
197
|
+
|
198
|
+
tx = create_burn_tx(funding_tx: funding_tx, color_id: color_id, sender: sender, amount: amount)
|
173
199
|
sender.internal_wallet.broadcast(tx)
|
174
200
|
end
|
175
201
|
|
@@ -11,18 +11,32 @@ module Glueby
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Create new public key, and new transaction that sends TPC to it
|
14
|
-
def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new, utxo_provider: nil)
|
15
|
+
if utxo_provider
|
16
|
+
script_pubkey = script ? script : Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
|
17
|
+
funding_tx, _index = utxo_provider.get_utxo(script_pubkey, FUNDING_TX_AMOUNT)
|
18
|
+
utxo_provider.wallet.sign_tx(funding_tx)
|
19
|
+
else
|
20
|
+
txb = Tapyrus::TxBuilder.new
|
21
|
+
fee = fee_estimator.fee(dummy_tx(txb.build))
|
22
|
+
|
23
|
+
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + FUNDING_TX_AMOUNT)
|
24
|
+
outputs.each do |utxo|
|
25
|
+
txb.add_utxo({
|
26
|
+
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
27
|
+
txid: utxo[:txid],
|
28
|
+
index: utxo[:vout],
|
29
|
+
value: utxo[:amount]
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
receiver_address = script ? script.addresses.first : wallet.internal_wallet.receive_address
|
34
|
+
tx = txb.pay(receiver_address, FUNDING_TX_AMOUNT)
|
35
|
+
.change_address(wallet.internal_wallet.change_address)
|
36
|
+
.fee(fee)
|
37
|
+
.build
|
38
|
+
wallet.internal_wallet.sign_tx(tx)
|
39
|
+
end
|
26
40
|
end
|
27
41
|
|
28
42
|
def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
@@ -48,21 +62,27 @@ module Glueby
|
|
48
62
|
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
49
63
|
end
|
50
64
|
|
51
|
-
def create_issue_tx_for_non_reissuable_token(issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
52
|
-
create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount, fee_estimator: fee_estimator)
|
65
|
+
def create_issue_tx_for_non_reissuable_token(funding_tx: nil, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
66
|
+
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)
|
53
67
|
end
|
54
68
|
|
55
|
-
def create_issue_tx_for_nft_token(issuer:, fee_estimator: FixedFeeEstimator.new)
|
56
|
-
create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_estimator: fee_estimator)
|
69
|
+
def create_issue_tx_for_nft_token(funding_tx: nil, issuer:, fee_estimator: FixedFeeEstimator.new)
|
70
|
+
create_issue_tx_from_out_point(funding_tx: funding_tx, token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1, fee_estimator: fee_estimator)
|
57
71
|
end
|
58
72
|
|
59
|
-
def create_issue_tx_from_out_point(token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
73
|
+
def create_issue_tx_from_out_point(funding_tx: nil, token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
60
74
|
tx = Tapyrus::Tx.new
|
61
75
|
|
62
76
|
fee = fee_estimator.fee(dummy_issue_tx_from_out_point)
|
63
|
-
sum
|
64
|
-
|
65
|
-
|
77
|
+
sum = if funding_tx
|
78
|
+
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
79
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
|
80
|
+
funding_tx.outputs.first.value
|
81
|
+
else
|
82
|
+
sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee)
|
83
|
+
fill_input(tx, outputs)
|
84
|
+
sum
|
85
|
+
end
|
66
86
|
out_point = tx.inputs.first.out_point
|
67
87
|
color_id = case token_type
|
68
88
|
when Tapyrus::Color::TokenTypes::NON_REISSUABLE
|
@@ -78,7 +98,18 @@ module Glueby
|
|
78
98
|
tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
|
79
99
|
|
80
100
|
fill_change_tpc(tx, issuer, sum - fee)
|
81
|
-
|
101
|
+
prev_txs = if funding_tx
|
102
|
+
output = funding_tx.outputs.first
|
103
|
+
[{
|
104
|
+
txid: funding_tx.txid,
|
105
|
+
vout: 0,
|
106
|
+
scriptPubKey: output.script_pubkey.to_hex,
|
107
|
+
amount: output.value
|
108
|
+
}]
|
109
|
+
else
|
110
|
+
[]
|
111
|
+
end
|
112
|
+
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
82
113
|
end
|
83
114
|
|
84
115
|
def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_estimator: FixedFeeEstimator.new)
|
@@ -103,7 +134,7 @@ module Glueby
|
|
103
134
|
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
104
135
|
end
|
105
136
|
|
106
|
-
def create_transfer_tx(color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
137
|
+
def create_transfer_tx(funding_tx:nil, color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
107
138
|
tx = Tapyrus::Tx.new
|
108
139
|
|
109
140
|
utxos = sender.internal_wallet.list_unspent
|
@@ -117,14 +148,32 @@ module Glueby
|
|
117
148
|
fill_change_token(tx, sender, sum_token - amount, color_id)
|
118
149
|
|
119
150
|
fee = fee_estimator.fee(dummy_tx(tx))
|
120
|
-
sum_tpc
|
121
|
-
|
151
|
+
sum_tpc = if funding_tx
|
152
|
+
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
153
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
|
154
|
+
funding_tx.outputs.first.value
|
155
|
+
else
|
156
|
+
sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee)
|
157
|
+
fill_input(tx, outputs)
|
158
|
+
sum_tpc
|
159
|
+
end
|
122
160
|
|
123
161
|
fill_change_tpc(tx, sender, sum_tpc - fee)
|
124
|
-
|
162
|
+
prev_txs = if funding_tx
|
163
|
+
output = funding_tx.outputs.first
|
164
|
+
[{
|
165
|
+
txid: funding_tx.txid,
|
166
|
+
vout: 0,
|
167
|
+
scriptPubKey: output.script_pubkey.to_hex,
|
168
|
+
amount: output.value
|
169
|
+
}]
|
170
|
+
else
|
171
|
+
[]
|
172
|
+
end
|
173
|
+
sender.internal_wallet.sign_tx(tx, prev_txs)
|
125
174
|
end
|
126
175
|
|
127
|
-
def create_burn_tx(color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
|
176
|
+
def create_burn_tx(funding_tx:nil, color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
|
128
177
|
tx = Tapyrus::Tx.new
|
129
178
|
|
130
179
|
utxos = sender.internal_wallet.list_unspent
|
@@ -135,12 +184,30 @@ module Glueby
|
|
135
184
|
|
136
185
|
fee = fee_estimator.fee(dummy_tx(tx))
|
137
186
|
|
138
|
-
|
139
|
-
|
140
|
-
|
187
|
+
sum_tpc = if funding_tx
|
188
|
+
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
189
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
|
190
|
+
funding_tx.outputs.first.value
|
191
|
+
else
|
192
|
+
dust = 600 # in case that the wallet has output which has just fee amount.
|
193
|
+
sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + dust)
|
194
|
+
fill_input(tx, outputs)
|
195
|
+
sum_tpc
|
196
|
+
end
|
141
197
|
|
142
198
|
fill_change_tpc(tx, sender, sum_tpc - fee)
|
143
|
-
|
199
|
+
prev_txs = if funding_tx
|
200
|
+
output = funding_tx.outputs.first
|
201
|
+
[{
|
202
|
+
txid: funding_tx.txid,
|
203
|
+
vout: 0,
|
204
|
+
scriptPubKey: output.script_pubkey.to_hex,
|
205
|
+
amount: output.value
|
206
|
+
}]
|
207
|
+
else
|
208
|
+
[]
|
209
|
+
end
|
210
|
+
sender.internal_wallet.sign_tx(tx, prev_txs)
|
144
211
|
end
|
145
212
|
|
146
213
|
def fill_input(tx, outputs)
|
data/lib/glueby/fee_provider.rb
CHANGED
@@ -8,6 +8,7 @@ module Glueby
|
|
8
8
|
WALLET_ID = 'FEE_PROVIDER_WALLET'
|
9
9
|
DEFAULT_FIXED_FEE = 1000
|
10
10
|
DEFAULT_UTXO_POOL_SIZE = 20
|
11
|
+
MAX_UTXO_POOL_SIZE = 2_000
|
11
12
|
|
12
13
|
attr_reader :fixed_fee, :utxo_pool_size, :wallet,
|
13
14
|
|
@@ -33,6 +34,7 @@ module Glueby
|
|
33
34
|
Internal::Wallet.create(WALLET_ID)
|
34
35
|
end
|
35
36
|
|
37
|
+
validate_config!
|
36
38
|
@fixed_fee = (FeeProvider.config && FeeProvider.config[:fixed_fee]) || DEFAULT_FIXED_FEE
|
37
39
|
@utxo_pool_size = (FeeProvider.config && FeeProvider.config[:utxo_pool_size]) || DEFAULT_UTXO_POOL_SIZE
|
38
40
|
end
|
@@ -69,5 +71,14 @@ module Glueby
|
|
69
71
|
def get_signature(script_sig)
|
70
72
|
script_sig.chunks.first.pushed_data
|
71
73
|
end
|
74
|
+
|
75
|
+
def validate_config!
|
76
|
+
if FeeProvider.config
|
77
|
+
utxo_pool_size = FeeProvider.config[:utxo_pool_size]
|
78
|
+
if utxo_pool_size && (!utxo_pool_size.is_a?(Integer) || utxo_pool_size > MAX_UTXO_POOL_SIZE)
|
79
|
+
raise Glueby::Configuration::Errors::InvalidConfiguration, "utxo_pool_size(#{utxo_pool_size}) should not be greater than #{MAX_UTXO_POOL_SIZE}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
72
83
|
end
|
73
84
|
end
|
@@ -72,6 +72,8 @@ module Glueby
|
|
72
72
|
# @param [Boolean] only_finalized - The UTXOs includes only finalized UTXO value if it
|
73
73
|
# is true. Default is true.
|
74
74
|
# @param [String] label - Label for filtering UTXOs
|
75
|
+
# - If label is nil or :unlabeled, only unlabeled UTXOs will be returned.
|
76
|
+
# - If label=:all, it will return all utxos
|
75
77
|
# @return [Array of UTXO]
|
76
78
|
#
|
77
79
|
# ## The UTXO structure
|
@@ -101,8 +103,10 @@ module Glueby
|
|
101
103
|
#
|
102
104
|
# @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method.
|
103
105
|
# @param [Tapyrus::Tx] tx - The transaction to be broadcasterd.
|
106
|
+
# @yield Option. If a block given, the block is called before actual broadcasting.
|
107
|
+
# @yieldparam [Tapyrus::Tx] tx - The tx that is going to be broadcasted as is.
|
104
108
|
# @return [String] txid
|
105
|
-
def broadcast(wallet_id, tx)
|
109
|
+
def broadcast(wallet_id, tx, &block)
|
106
110
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
107
111
|
end
|
108
112
|
|
@@ -93,8 +93,15 @@ module Glueby
|
|
93
93
|
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
94
94
|
utxos = wallet.utxos
|
95
95
|
utxos = utxos.where(status: :finalized) if only_finalized
|
96
|
-
|
97
|
-
|
96
|
+
|
97
|
+
if [:unlabeled, nil].include?(label)
|
98
|
+
utxos = utxos.where(label: nil)
|
99
|
+
elsif label && (label != :all)
|
100
|
+
utxos = utxos.where(label: label)
|
101
|
+
else
|
102
|
+
utxos
|
103
|
+
end
|
104
|
+
|
98
105
|
utxos.map do |utxo|
|
99
106
|
{
|
100
107
|
txid: utxo.txid,
|
@@ -102,7 +109,8 @@ module Glueby
|
|
102
109
|
script_pubkey: utxo.script_pubkey,
|
103
110
|
color_id: utxo.color_id,
|
104
111
|
amount: utxo.value,
|
105
|
-
finalized: utxo.status == 'finalized'
|
112
|
+
finalized: utxo.status == 'finalized',
|
113
|
+
label: utxo.label
|
106
114
|
}
|
107
115
|
end
|
108
116
|
end
|
@@ -112,10 +120,11 @@ module Glueby
|
|
112
120
|
wallet.sign(tx, prevtxs, sighashtype: sighashtype)
|
113
121
|
end
|
114
122
|
|
115
|
-
def broadcast(wallet_id, tx)
|
123
|
+
def broadcast(wallet_id, tx, &block)
|
116
124
|
::ActiveRecord::Base.transaction do
|
117
125
|
AR::Utxo.destroy_for_inputs(tx)
|
118
126
|
AR::Utxo.create_or_update_for_outputs(tx, status: :broadcasted)
|
127
|
+
block.call(tx) if block
|
119
128
|
Glueby::Internal::RPC.client.sendrawtransaction(tx.to_hex)
|
120
129
|
end
|
121
130
|
end
|
@@ -23,7 +23,6 @@ module Glueby
|
|
23
23
|
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
24
24
|
|
25
25
|
WALLET_PREFIX = 'wallet-'
|
26
|
-
ADDRESS_TYPE = 'legacy'
|
27
26
|
|
28
27
|
RPC_WALLET_ERROR_ERROR_CODE = -4 # Unspecified problem with wallet (key not found etc.)
|
29
28
|
RPC_WALLET_NOT_FOUND_ERROR_CODE = -18 # Invalid wallet specified
|
@@ -83,14 +82,21 @@ module Glueby
|
|
83
82
|
end
|
84
83
|
end
|
85
84
|
|
85
|
+
# If label=nil, it will return unlabeled utxos to protect labeled utxos for specific purpose
|
86
|
+
# If label=:all, it will return all utxos
|
86
87
|
def list_unspent(wallet_id, only_finalized = true, label = nil)
|
87
88
|
perform_as(wallet_id) do |client|
|
88
89
|
min_conf = only_finalized ? 1 : 0
|
89
90
|
res = client.listunspent(min_conf)
|
90
91
|
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
if [:unlabeled, nil].include?(label)
|
93
|
+
res = res.filter { |i| i['label'] == "" }
|
94
|
+
elsif label && (label != :all)
|
95
|
+
res = res.filter { |i| i['label'] == label }
|
96
|
+
else
|
97
|
+
res
|
98
|
+
end
|
99
|
+
|
94
100
|
res.map do |i|
|
95
101
|
script = Tapyrus::Script.parse_from_payload(i['scriptPubKey'].htb)
|
96
102
|
color_id = if script.cp2pkh? || script.cp2sh?
|
@@ -102,7 +108,8 @@ module Glueby
|
|
102
108
|
script_pubkey: i['scriptPubKey'],
|
103
109
|
color_id: color_id,
|
104
110
|
amount: tpc_to_tapyrus(i['amount']),
|
105
|
-
finalized: i['confirmations'] != 0
|
111
|
+
finalized: i['confirmations'] != 0,
|
112
|
+
label: i['label']
|
106
113
|
}
|
107
114
|
end
|
108
115
|
end
|
@@ -119,27 +126,28 @@ module Glueby
|
|
119
126
|
end
|
120
127
|
end
|
121
128
|
|
122
|
-
def broadcast(wallet_id, tx)
|
129
|
+
def broadcast(wallet_id, tx, &block)
|
123
130
|
perform_as(wallet_id) do |client|
|
131
|
+
block.call(tx) if block
|
124
132
|
client.sendrawtransaction(tx.to_hex)
|
125
133
|
end
|
126
134
|
end
|
127
135
|
|
128
136
|
def receive_address(wallet_id, label = nil)
|
129
137
|
perform_as(wallet_id) do |client|
|
130
|
-
client.getnewaddress(label || ''
|
138
|
+
client.getnewaddress(label || '')
|
131
139
|
end
|
132
140
|
end
|
133
141
|
|
134
142
|
def change_address(wallet_id)
|
135
143
|
perform_as(wallet_id) do |client|
|
136
|
-
client.getrawchangeaddress
|
144
|
+
client.getrawchangeaddress
|
137
145
|
end
|
138
146
|
end
|
139
147
|
|
140
148
|
def create_pubkey(wallet_id)
|
141
149
|
perform_as(wallet_id) do |client|
|
142
|
-
address = client.getnewaddress(''
|
150
|
+
address = client.getnewaddress('')
|
143
151
|
info = client.getaddressinfo(address)
|
144
152
|
Tapyrus::Key.new(pubkey: info['pubkey'])
|
145
153
|
end
|
@@ -84,9 +84,10 @@ module Glueby
|
|
84
84
|
|
85
85
|
# @param only_finalized [Boolean] The flag to get a UTXO with status only finalized
|
86
86
|
# @param label [String] This label is used to filtered the UTXOs with labeled if a key or Utxo is labeled.
|
87
|
-
# - If label is
|
88
|
-
# - If label=:
|
89
|
-
def list_unspent(only_finalized = true, label =
|
87
|
+
# - If label is nil or :unlabeled, only unlabeled UTXOs will be returned.
|
88
|
+
# - If label=:all, all UTXOs will be returned.
|
89
|
+
def list_unspent(only_finalized = true, label = :unlabeled)
|
90
|
+
label = :unlabeled unless label
|
90
91
|
wallet_adapter.list_unspent(id, only_finalized, label)
|
91
92
|
end
|
92
93
|
|
@@ -117,10 +118,7 @@ module Glueby
|
|
117
118
|
# @param [Proc] block The block that is called before broadcasting. It can be used to handle tx that is modified by FeeProvider.
|
118
119
|
def broadcast(tx, without_fee_provider: false, &block)
|
119
120
|
tx = FeeProvider.provide(tx) if !without_fee_provider && Glueby.configuration.fee_provider_bears?
|
120
|
-
|
121
|
-
block.call(tx) if block
|
122
|
-
|
123
|
-
wallet_adapter.broadcast(id, tx)
|
121
|
+
wallet_adapter.broadcast(id, tx, &block)
|
124
122
|
tx
|
125
123
|
end
|
126
124
|
|
@@ -136,8 +134,9 @@ module Glueby
|
|
136
134
|
wallet_adapter.create_pubkey(id)
|
137
135
|
end
|
138
136
|
|
139
|
-
def collect_uncolored_outputs(amount, label = nil, only_finalized = true)
|
137
|
+
def collect_uncolored_outputs(amount, label = nil, only_finalized = true, shuffle = false)
|
140
138
|
utxos = list_unspent(only_finalized, label)
|
139
|
+
utxos.shuffle! if shuffle
|
141
140
|
|
142
141
|
utxos.inject([0, []]) do |sum, output|
|
143
142
|
next sum if output[:color_id]
|
data/lib/glueby/railtie.rb
CHANGED
@@ -0,0 +1,135 @@
|
|
1
|
+
module Glueby
|
2
|
+
class UtxoProvider
|
3
|
+
class Tasks
|
4
|
+
include Glueby::Contract::TxBuilder
|
5
|
+
|
6
|
+
attr_reader :utxo_provider
|
7
|
+
|
8
|
+
STATUS = {
|
9
|
+
# UtxoProvider is ready to pay tpcs.
|
10
|
+
ready: 'Ready',
|
11
|
+
# UtxoProvider is ready to pay tpcs, but it doesn't have enough amount to fill the UTXO pool by UTXOs which is for paying tpcs.
|
12
|
+
insufficient_amount: 'Insufficient Amount',
|
13
|
+
# UtxoProvider is not ready to pay tpcs. It has no UTXOs for paying amounts.
|
14
|
+
not_ready: 'Not Ready'
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@utxo_provider = Glueby::UtxoProvider.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Create UTXOs for paying tpc
|
22
|
+
#
|
23
|
+
# UtxoProvider have the UTXO pool. the pool is manged to keep some number of UTXOs that have fixed value. The
|
24
|
+
# value is configurable by :default_value. This method do the management to the pool.
|
25
|
+
def manage_utxo_pool
|
26
|
+
txb = Tapyrus::TxBuilder.new
|
27
|
+
|
28
|
+
sum, utxos = collect_outputs
|
29
|
+
return if utxos.empty?
|
30
|
+
|
31
|
+
utxos.each { |utxo| txb.add_utxo(utxo) }
|
32
|
+
address = wallet.receive_address
|
33
|
+
|
34
|
+
shortage = [utxo_provider.utxo_pool_size - current_utxo_pool_size, 0].max
|
35
|
+
return if shortage == 0
|
36
|
+
|
37
|
+
added_outputs = 0
|
38
|
+
shortage.times do
|
39
|
+
fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
|
40
|
+
break if (sum - fee) < utxo_provider.default_value
|
41
|
+
txb.pay(address, utxo_provider.default_value)
|
42
|
+
sum -= utxo_provider.default_value
|
43
|
+
added_outputs += 1
|
44
|
+
end
|
45
|
+
|
46
|
+
return if added_outputs == 0
|
47
|
+
|
48
|
+
fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
|
49
|
+
tx = txb.change_address(address)
|
50
|
+
.fee(fee)
|
51
|
+
.build
|
52
|
+
tx = wallet.sign_tx(tx)
|
53
|
+
wallet.broadcast(tx)
|
54
|
+
ensure
|
55
|
+
status
|
56
|
+
end
|
57
|
+
|
58
|
+
# Show the status of the UTXO pool
|
59
|
+
def status
|
60
|
+
status = :ready
|
61
|
+
|
62
|
+
if current_utxo_pool_size < utxo_provider.utxo_pool_size
|
63
|
+
if tpc_amount < value_to_fill_utxo_pool
|
64
|
+
status = :insufficient_amount
|
65
|
+
message = <<~MESSAGE
|
66
|
+
1. Please replenishment TPC which is for paying tpc to UtxoProvider.
|
67
|
+
UtxoProvider needs #{value_to_fill_utxo_pool} tapyrus in UTXO pool.
|
68
|
+
UtxoProvider wallet's address is '#{wallet.receive_address}'
|
69
|
+
2. Then create UTXOs for paying in UTXO pool with 'rake glueby:utxo_provider:manage_utxo_pool'
|
70
|
+
MESSAGE
|
71
|
+
else
|
72
|
+
message = "Please create UTXOs for paying in UTXO pool with 'rake glueby:utxo_provider:manage_utxo_pool'\n"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
status = :not_ready if current_utxo_pool_size == 0
|
77
|
+
|
78
|
+
puts <<~EOS
|
79
|
+
Status: #{STATUS[status]}
|
80
|
+
TPC amount: #{delimit(tpc_amount)}
|
81
|
+
UTXO pool size: #{delimit(current_utxo_pool_size)}
|
82
|
+
#{"\n" if message}#{message}
|
83
|
+
Configuration:
|
84
|
+
default_value = #{delimit(utxo_provider.default_value)}
|
85
|
+
utxo_pool_size = #{delimit(utxo_provider.utxo_pool_size)}
|
86
|
+
EOS
|
87
|
+
end
|
88
|
+
|
89
|
+
# Show the address of Utxo Provider
|
90
|
+
def print_address
|
91
|
+
puts wallet.receive_address
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def tpc_amount
|
97
|
+
wallet.balance(false)
|
98
|
+
end
|
99
|
+
|
100
|
+
def collect_outputs
|
101
|
+
wallet.list_unspent.inject([0, []]) do |sum, output|
|
102
|
+
next sum if output[:color_id] || output[:amount] == utxo_provider.default_value
|
103
|
+
|
104
|
+
new_sum = sum[0] + output[:amount]
|
105
|
+
new_outputs = sum[1] << {
|
106
|
+
txid: output[:txid],
|
107
|
+
script_pubkey: output[:script_pubkey],
|
108
|
+
value: output[:amount],
|
109
|
+
index: output[:vout] ,
|
110
|
+
finalized: output[:finalized]
|
111
|
+
}
|
112
|
+
[new_sum, new_outputs]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def current_utxo_pool_size
|
117
|
+
wallet
|
118
|
+
.list_unspent(false)
|
119
|
+
.count { |o| !o[:color_id] && o[:amount] == utxo_provider.default_value }
|
120
|
+
end
|
121
|
+
|
122
|
+
def value_to_fill_utxo_pool
|
123
|
+
utxo_provider.default_value * utxo_provider.utxo_pool_size
|
124
|
+
end
|
125
|
+
|
126
|
+
def wallet
|
127
|
+
utxo_provider.wallet
|
128
|
+
end
|
129
|
+
|
130
|
+
def delimit(num)
|
131
|
+
num.to_s.reverse.scan(/.{1,3}/).join('_').reverse
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Glueby
|
2
|
+
class UtxoProvider
|
3
|
+
include Glueby::Contract::TxBuilder
|
4
|
+
|
5
|
+
autoload :Tasks, 'glueby/utxo_provider/tasks'
|
6
|
+
|
7
|
+
WALLET_ID = 'UTXO_PROVIDER_WALLET'
|
8
|
+
DEFAULT_VALUE = 1_000
|
9
|
+
DEFAULT_UTXO_POOL_SIZE = 20
|
10
|
+
MAX_UTXO_POOL_SIZE = 2_000
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_reader :config
|
14
|
+
|
15
|
+
# @param [Hash] config
|
16
|
+
# @option config [Integer] :default_value
|
17
|
+
# @option opts [Integer] :utxo_pool_size
|
18
|
+
# @option opts [Glueby::Contract::FeeEstimator] :fee_estimator
|
19
|
+
def configure(config)
|
20
|
+
@config = config
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@wallet = load_wallet
|
26
|
+
validate_config!
|
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
|
+
end
|
31
|
+
|
32
|
+
attr_reader :wallet, :fee_estimator, :default_value, :utxo_pool_size
|
33
|
+
|
34
|
+
# Provide a UTXO
|
35
|
+
# @param [Tapyrus::Script] script_pubkey The script to be provided
|
36
|
+
# @param [Integer] value The tpc amount to be provided
|
37
|
+
# @return [Array<(Tapyrus::Tx, Integer)>]
|
38
|
+
# The tx that has a UTXO to be provided in its outputs.
|
39
|
+
# The output index in the tx to indicate the place of a provided UTXO.
|
40
|
+
# @raise [Glueby::Contract::Errors::InsufficientFunds] if provider does not have any utxo which has specified value.
|
41
|
+
def get_utxo(script_pubkey, value = DEFAULT_VALUE)
|
42
|
+
txb = Tapyrus::TxBuilder.new
|
43
|
+
txb.pay(script_pubkey.addresses.first, value)
|
44
|
+
|
45
|
+
fee = fee_estimator.fee(dummy_tx(txb.build))
|
46
|
+
# The outputs need to be shuffled so that no utxos are spent twice as possible.
|
47
|
+
sum, outputs = wallet.collect_uncolored_outputs(fee + value, nil, true, true)
|
48
|
+
|
49
|
+
outputs.each do |utxo|
|
50
|
+
txb.add_utxo({
|
51
|
+
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
52
|
+
txid: utxo[:txid],
|
53
|
+
index: utxo[:vout],
|
54
|
+
value: utxo[:amount]
|
55
|
+
})
|
56
|
+
end
|
57
|
+
|
58
|
+
txb.fee(fee).change_address(wallet.change_address)
|
59
|
+
|
60
|
+
tx = txb.build
|
61
|
+
signed_tx = wallet.sign_tx(tx)
|
62
|
+
[signed_tx, 0]
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Create wallet for provider
|
68
|
+
def load_wallet
|
69
|
+
begin
|
70
|
+
Glueby::Internal::Wallet.load(WALLET_ID)
|
71
|
+
rescue Glueby::Internal::Wallet::Errors::WalletNotFound => _
|
72
|
+
Glueby::Internal::Wallet.create(WALLET_ID)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_config!
|
77
|
+
if UtxoProvider.config
|
78
|
+
utxo_pool_size = UtxoProvider.config[:utxo_pool_size]
|
79
|
+
if utxo_pool_size && (!utxo_pool_size.is_a?(Integer) || utxo_pool_size > MAX_UTXO_POOL_SIZE)
|
80
|
+
raise Glueby::Configuration::Errors::InvalidConfiguration, "utxo_pool_size(#{utxo_pool_size}) should not be greater than #{MAX_UTXO_POOL_SIZE}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/glueby/version.rb
CHANGED
data/lib/glueby.rb
CHANGED
@@ -10,6 +10,7 @@ module Glueby
|
|
10
10
|
autoload :FeeProvider, 'glueby/fee_provider'
|
11
11
|
autoload :Configuration, 'glueby/configuration'
|
12
12
|
autoload :BlockSyncer, 'glueby/block_syncer'
|
13
|
+
autoload :UtxoProvider, 'glueby/utxo_provider'
|
13
14
|
|
14
15
|
if defined? ::Rails::Railtie
|
15
16
|
require 'glueby/railtie'
|
@@ -8,15 +8,22 @@ 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?
|
11
12
|
timestamps.each do |t|
|
12
13
|
begin
|
14
|
+
wallet = Glueby::Wallet.load(t.wallet_id)
|
15
|
+
funding_tx, tx = create_txs(wallet, t.prefix, t.content_hash, Glueby::Contract::FixedFeeEstimator.new, utxo_provider)
|
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
|
13
22
|
::ActiveRecord::Base.transaction do
|
14
|
-
wallet = Glueby::Wallet.load(t.wallet_id)
|
15
|
-
tx = create_tx(wallet, t.prefix, t.content_hash, Glueby::Contract::FixedFeeEstimator.new)
|
16
23
|
wallet.internal_wallet.broadcast(tx) do |tx|
|
17
24
|
t.update(txid: tx.txid, status: :unconfirmed)
|
18
25
|
end
|
19
|
-
puts "broadcasted (id=#{t.id}, txid=#{tx.txid})"
|
26
|
+
puts "timestamp tx was broadcasted (id=#{t.id}, txid=#{tx.txid})"
|
20
27
|
end
|
21
28
|
rescue => e
|
22
29
|
puts "failed to broadcast (id=#{t.id}, reason=#{e.message})"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
namespace :glueby do
|
2
|
+
namespace :utxo_provider do
|
3
|
+
desc 'Manage the UTXO pool in Glueby::UtxoProvider. Creates outputs for paying utxo if the outputs is less than configured pool size by :utxo_pool_size'
|
4
|
+
task :manage_utxo_pool, [] => [:environment] do |_, _|
|
5
|
+
Glueby::UtxoProvider::Tasks.new.manage_utxo_pool
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Show the status of the UTXO pool in Glueby::UtxoProvider'
|
9
|
+
task :status, [] => [:environment] do |_, _|
|
10
|
+
Glueby::UtxoProvider::Tasks.new.status
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Show the address of the Glueby::UtxoProvider'
|
14
|
+
task :address, [] => [:environment] do |_, _|
|
15
|
+
Glueby::UtxoProvider::Tasks.new.print_address
|
16
|
+
end
|
17
|
+
end
|
18
|
+
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.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tapyrus
|
@@ -131,12 +131,15 @@ files:
|
|
131
131
|
- lib/glueby/internal/wallet/errors.rb
|
132
132
|
- lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb
|
133
133
|
- lib/glueby/railtie.rb
|
134
|
+
- lib/glueby/utxo_provider.rb
|
135
|
+
- lib/glueby/utxo_provider/tasks.rb
|
134
136
|
- lib/glueby/version.rb
|
135
137
|
- lib/glueby/wallet.rb
|
136
138
|
- lib/tasks/glueby/block_syncer.rake
|
137
139
|
- lib/tasks/glueby/contract.rake
|
138
140
|
- lib/tasks/glueby/contract/timestamp.rake
|
139
141
|
- lib/tasks/glueby/fee_provider.rake
|
142
|
+
- lib/tasks/glueby/utxo_provider.rake
|
140
143
|
homepage: https://github.com/chaintope/glueby
|
141
144
|
licenses:
|
142
145
|
- MIT
|