glueby 0.3.0 → 0.4.0
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/.github/workflows/ruby.yml +35 -0
- data/Gemfile +1 -0
- data/README.md +111 -6
- data/glueby.gemspec +1 -1
- data/lib/generators/glueby/contract/reissuable_token_generator.rb +26 -0
- data/lib/generators/glueby/contract/templates/key_table.rb.erb +3 -3
- data/lib/generators/glueby/contract/templates/reissuable_token_table.rb.erb +10 -0
- data/lib/generators/glueby/contract/templates/system_information_table.rb.erb +2 -2
- data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +1 -1
- data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +2 -2
- data/lib/generators/glueby/contract/templates/wallet_table.rb.erb +2 -2
- data/lib/glueby.rb +25 -0
- data/lib/glueby/configuration.rb +62 -0
- data/lib/glueby/contract.rb +2 -2
- data/lib/glueby/contract/active_record.rb +1 -0
- data/lib/glueby/contract/active_record/reissuable_token.rb +26 -0
- data/lib/glueby/contract/fee_estimator.rb +38 -0
- data/lib/glueby/contract/payment.rb +4 -4
- data/lib/glueby/contract/timestamp.rb +6 -6
- data/lib/glueby/contract/token.rb +69 -22
- data/lib/glueby/contract/tx_builder.rb +22 -19
- data/lib/glueby/fee_provider.rb +73 -0
- data/lib/glueby/fee_provider/tasks.rb +136 -0
- data/lib/glueby/generator/migrate_generator.rb +1 -1
- data/lib/glueby/internal/wallet.rb +28 -4
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +18 -3
- data/lib/glueby/internal/wallet/active_record/wallet.rb +15 -5
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +15 -5
- data/lib/glueby/internal/wallet/errors.rb +3 -0
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +36 -11
- data/lib/glueby/version.rb +1 -1
- data/lib/glueby/wallet.rb +3 -2
- data/lib/tasks/glueby/contract/timestamp.rake +1 -1
- data/lib/tasks/glueby/fee_provider.rake +13 -0
- metadata +16 -9
- data/.travis.yml +0 -7
- data/lib/glueby/contract/fee_provider.rb +0 -21
@@ -0,0 +1,26 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
module AR
|
4
|
+
class ReissuableToken < ::ActiveRecord::Base
|
5
|
+
|
6
|
+
# Get the script_pubkey corresponding to the color_id in Tapyrus::Script format
|
7
|
+
# @param [String] color_id
|
8
|
+
# @return [Tapyrus::Script]
|
9
|
+
def self.script_pubkey(color_id)
|
10
|
+
script_pubkey = Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).pluck(:script_pubkey).first
|
11
|
+
if script_pubkey
|
12
|
+
Tapyrus::Script.parse_from_payload(script_pubkey.htb)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check if the color_id is already stored
|
17
|
+
# @param [String] color_id
|
18
|
+
# @return [Boolean]
|
19
|
+
def self.saved?(color_id)
|
20
|
+
Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).exists?
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
module FeeEstimator
|
4
|
+
# @param [Tapyrus::Tx] tx - The target tx
|
5
|
+
# @return fee by tapyrus(not TPC).
|
6
|
+
def fee(tx)
|
7
|
+
return 0 if Glueby.configuration.fee_provider_bears?
|
8
|
+
estimate_fee(tx)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# @private
|
14
|
+
# @abstract Override in subclasses. This is would be implemented an actual estimation logic.
|
15
|
+
# @param [Tapyrus::Tx] tx - The target tx
|
16
|
+
# @return fee by tapyrus(not TPC).
|
17
|
+
def estimate_fee(tx)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class FixedFeeEstimator
|
23
|
+
include FeeEstimator
|
24
|
+
|
25
|
+
def initialize(fixed_fee: 10_000)
|
26
|
+
@fixed_fee = fixed_fee
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @private
|
32
|
+
# @return fee by tapyrus(not TPC).
|
33
|
+
def estimate_fee(_tx)
|
34
|
+
@fixed_fee
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -32,11 +32,11 @@ module Glueby
|
|
32
32
|
extend Glueby::Contract::TxBuilder
|
33
33
|
|
34
34
|
class << self
|
35
|
-
def transfer(sender:, receiver_address:, amount:,
|
35
|
+
def transfer(sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
36
36
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
37
37
|
|
38
38
|
tx = Tapyrus::Tx.new
|
39
|
-
dummy_fee =
|
39
|
+
dummy_fee = fee_estimator.fee(dummy_tx(tx))
|
40
40
|
|
41
41
|
sum, outputs = sender.internal_wallet.collect_uncolored_outputs(dummy_fee + amount)
|
42
42
|
fill_input(tx, outputs)
|
@@ -44,13 +44,13 @@ module Glueby
|
|
44
44
|
receiver_script = Tapyrus::Script.parse_from_addr(receiver_address)
|
45
45
|
tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_script)
|
46
46
|
|
47
|
-
fee =
|
47
|
+
fee = fee_estimator.fee(tx)
|
48
48
|
|
49
49
|
fill_change_tpc(tx, sender, sum - fee - amount)
|
50
50
|
|
51
51
|
tx = sender.internal_wallet.sign_tx(tx)
|
52
52
|
|
53
|
-
|
53
|
+
sender.internal_wallet.broadcast(tx)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -14,11 +14,11 @@ module Glueby
|
|
14
14
|
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
15
15
|
module_function
|
16
16
|
|
17
|
-
def create_tx(wallet, prefix, data,
|
17
|
+
def create_tx(wallet, prefix, data, fee_estimator)
|
18
18
|
tx = Tapyrus::Tx.new
|
19
19
|
tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: create_script(prefix, data))
|
20
20
|
|
21
|
-
fee =
|
21
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
22
22
|
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
|
23
23
|
fill_input(tx, outputs)
|
24
24
|
|
@@ -50,7 +50,7 @@ module Glueby
|
|
50
50
|
|
51
51
|
# @param [String] content Data to be hashed and stored in blockchain.
|
52
52
|
# @param [String] prefix prefix of op_return data
|
53
|
-
# @param [Glueby::Contract::
|
53
|
+
# @param [Glueby::Contract::FeeEstimator] fee_estimator
|
54
54
|
# @param [Symbol] digest type which select of:
|
55
55
|
# - :sha256
|
56
56
|
# - :double_sha256
|
@@ -60,13 +60,13 @@ module Glueby
|
|
60
60
|
wallet:,
|
61
61
|
content:,
|
62
62
|
prefix: '',
|
63
|
-
|
63
|
+
fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
|
64
64
|
digest: :sha256
|
65
65
|
)
|
66
66
|
@wallet = wallet
|
67
67
|
@content = content
|
68
68
|
@prefix = prefix
|
69
|
-
@
|
69
|
+
@fee_estimator = fee_estimator
|
70
70
|
@digest = digest
|
71
71
|
end
|
72
72
|
|
@@ -77,7 +77,7 @@ module Glueby
|
|
77
77
|
def save!
|
78
78
|
raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
|
79
79
|
|
80
|
-
@tx = create_tx(@wallet, @prefix, digest_content, @
|
80
|
+
@tx = create_tx(@wallet, @prefix, digest_content, @fee_estimator)
|
81
81
|
@txid = @wallet.internal_wallet.broadcast(@tx)
|
82
82
|
end
|
83
83
|
|
@@ -55,14 +55,14 @@ module Glueby
|
|
55
55
|
# @param issuer [Glueby::Wallet]
|
56
56
|
# @param token_type [TokenTypes]
|
57
57
|
# @param amount [Integer]
|
58
|
-
# @return [
|
58
|
+
# @return [Array<token, Array<tx>>] Tuple of tx array and token object
|
59
59
|
# @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
|
60
60
|
# @raise [InvalidAmount] if amount is not positive integer.
|
61
61
|
# @raise [UnspportedTokenType] if token is not supported.
|
62
62
|
def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1)
|
63
63
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
64
64
|
|
65
|
-
txs, color_id
|
65
|
+
txs, color_id = case token_type
|
66
66
|
when Tapyrus::Color::TokenTypes::REISSUABLE
|
67
67
|
issue_reissuable_token(issuer: issuer, amount: amount)
|
68
68
|
when Tapyrus::Color::TokenTypes::NON_REISSUABLE
|
@@ -72,33 +72,44 @@ module Glueby
|
|
72
72
|
else
|
73
73
|
raise Glueby::Contract::Errors::UnsupportedTokenType
|
74
74
|
end
|
75
|
-
|
76
|
-
new(color_id: color_id,
|
75
|
+
|
76
|
+
[new(color_id: color_id), txs]
|
77
77
|
end
|
78
78
|
|
79
79
|
private
|
80
80
|
|
81
81
|
def issue_reissuable_token(issuer:, amount:)
|
82
|
-
|
83
|
-
funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee)
|
84
|
-
tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
|
82
|
+
funding_tx = create_funding_tx(wallet: issuer)
|
85
83
|
script_pubkey = funding_tx.outputs.first.script_pubkey
|
86
84
|
color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
|
87
|
-
|
85
|
+
|
86
|
+
ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
|
87
|
+
# Store the script_pubkey for reissue the token.
|
88
|
+
Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
|
89
|
+
|
90
|
+
funding_tx = issuer.internal_wallet.broadcast(funding_tx)
|
91
|
+
tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
|
92
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
93
|
+
[[funding_tx, tx], color_id]
|
94
|
+
end
|
88
95
|
end
|
89
96
|
|
90
97
|
def issue_non_reissuable_token(issuer:, amount:)
|
91
98
|
tx = create_issue_tx_for_non_reissuable_token(issuer: issuer, amount: amount)
|
99
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
100
|
+
|
92
101
|
out_point = tx.inputs.first.out_point
|
93
102
|
color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
|
94
|
-
[[tx], color_id
|
103
|
+
[[tx], color_id]
|
95
104
|
end
|
96
105
|
|
97
106
|
def issue_nft_token(issuer:)
|
98
107
|
tx = create_issue_tx_for_nft_token(issuer: issuer)
|
108
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
109
|
+
|
99
110
|
out_point = tx.inputs.first.out_point
|
100
111
|
color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
|
101
|
-
[[tx], color_id
|
112
|
+
[[tx], color_id]
|
102
113
|
end
|
103
114
|
end
|
104
115
|
|
@@ -108,6 +119,7 @@ module Glueby
|
|
108
119
|
# A wallet can issue the token only when it is REISSUABLE token.
|
109
120
|
# @param issuer [Glueby::Wallet]
|
110
121
|
# @param amount [Integer]
|
122
|
+
# @return [Array<String, tx>] Tuple of color_id and tx object
|
111
123
|
# @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
|
112
124
|
# @raise [InvalidAmount] if amount is not positive integer.
|
113
125
|
# @raise [InvalidTokenType] if token is not reissuable.
|
@@ -115,12 +127,17 @@ module Glueby
|
|
115
127
|
def reissue!(issuer:, amount:)
|
116
128
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
117
129
|
raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
|
118
|
-
raise Glueby::Contract::Errors::UnknownScriptPubkey unless @script_pubkey
|
119
130
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
131
|
+
if validate_reissuer(wallet: issuer)
|
132
|
+
funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey)
|
133
|
+
funding_tx = issuer.internal_wallet.broadcast(funding_tx)
|
134
|
+
tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
|
135
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
136
|
+
|
137
|
+
[color_id, tx]
|
138
|
+
else
|
139
|
+
raise Glueby::Contract::Errors::UnknownScriptPubkey
|
140
|
+
end
|
124
141
|
end
|
125
142
|
|
126
143
|
# Send the token to other wallet
|
@@ -128,7 +145,7 @@ module Glueby
|
|
128
145
|
# @param sender [Glueby::Wallet] wallet to send this token
|
129
146
|
# @param receiver_address [String] address to receive this token
|
130
147
|
# @param amount [Integer]
|
131
|
-
# @return [
|
148
|
+
# @return [Array<String, tx>] Tuple of color_id and tx object
|
132
149
|
# @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
|
133
150
|
# @raise [InsufficientTokens] if wallet does not have enough token to send.
|
134
151
|
# @raise [InvalidAmount] if amount is not positive integer.
|
@@ -137,6 +154,7 @@ module Glueby
|
|
137
154
|
|
138
155
|
tx = create_transfer_tx(color_id: color_id, sender: sender, receiver_address: receiver_address, amount: amount)
|
139
156
|
sender.internal_wallet.broadcast(tx)
|
157
|
+
[color_id, tx]
|
140
158
|
end
|
141
159
|
|
142
160
|
# Burn token
|
@@ -170,12 +188,19 @@ module Glueby
|
|
170
188
|
color_id.type
|
171
189
|
end
|
172
190
|
|
191
|
+
# Return the script_pubkey of the token from ActiveRecord
|
192
|
+
# @return [String] script_pubkey
|
193
|
+
def script_pubkey
|
194
|
+
@script_pubkey ||= Glueby::Contract::AR::ReissuableToken.script_pubkey(@color_id.to_hex)
|
195
|
+
end
|
196
|
+
|
173
197
|
# Return serialized payload
|
174
198
|
# @return [String] payload
|
175
199
|
def to_payload
|
176
200
|
payload = +''
|
177
201
|
payload << @color_id.to_payload
|
178
|
-
payload << @script_pubkey.to_payload if
|
202
|
+
payload << @script_pubkey.to_payload if script_pubkey
|
203
|
+
payload
|
179
204
|
end
|
180
205
|
|
181
206
|
# Restore token from payload
|
@@ -184,14 +209,36 @@ module Glueby
|
|
184
209
|
def self.parse_from_payload(payload)
|
185
210
|
color_id, script_pubkey = payload.unpack('a33a*')
|
186
211
|
color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(color_id)
|
187
|
-
|
188
|
-
|
212
|
+
if color_id.type == Tapyrus::Color::TokenTypes::REISSUABLE
|
213
|
+
raise ArgumentError, 'script_pubkey should not be empty' if script_pubkey.empty?
|
214
|
+
script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey)
|
215
|
+
Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
|
216
|
+
end
|
217
|
+
new(color_id: color_id)
|
189
218
|
end
|
190
219
|
|
191
|
-
|
220
|
+
# Generate Token Instance
|
221
|
+
# @param color_id [String]
|
222
|
+
def initialize(color_id:)
|
192
223
|
@color_id = color_id
|
193
|
-
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
# Verify that wallet is the issuer of the reissuable token
|
229
|
+
# reutrn [Boolean]
|
230
|
+
def validate_reissuer(wallet:)
|
231
|
+
addresses = wallet.internal_wallet.get_addresses
|
232
|
+
addresses.each do |address|
|
233
|
+
decoded_address = Tapyrus.decode_base58_address(address)
|
234
|
+
pubkey_hash_from_address = decoded_address[0]
|
235
|
+
pubkey_hash_from_script = Tapyrus::Script.parse_from_payload(script_pubkey.chunks[2])
|
236
|
+
if pubkey_hash_from_address == pubkey_hash_from_script.to_s
|
237
|
+
return true
|
238
|
+
end
|
239
|
+
end
|
240
|
+
false
|
194
241
|
end
|
195
242
|
end
|
196
243
|
end
|
197
|
-
end
|
244
|
+
end
|
@@ -3,26 +3,29 @@
|
|
3
3
|
module Glueby
|
4
4
|
module Contract
|
5
5
|
module TxBuilder
|
6
|
+
# The amount of output in funding tx for Reissuable token.
|
7
|
+
FUNDING_TX_AMOUNT = 10_000
|
8
|
+
|
6
9
|
def receive_address(wallet:)
|
7
10
|
wallet.receive_address
|
8
11
|
end
|
9
12
|
|
10
13
|
# Create new public key, and new transaction that sends TPC to it
|
11
|
-
def create_funding_tx(wallet:,
|
14
|
+
def create_funding_tx(wallet:, script: nil, fee_estimator: FixedFeeEstimator.new)
|
12
15
|
tx = Tapyrus::Tx.new
|
13
|
-
fee =
|
16
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
14
17
|
|
15
|
-
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee +
|
18
|
+
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee + FUNDING_TX_AMOUNT)
|
16
19
|
fill_input(tx, outputs)
|
17
20
|
|
18
21
|
receiver_script = script ? script : Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
|
19
|
-
tx.outputs << Tapyrus::TxOut.new(value:
|
22
|
+
tx.outputs << Tapyrus::TxOut.new(value: FUNDING_TX_AMOUNT, script_pubkey: receiver_script)
|
20
23
|
|
21
|
-
fill_change_tpc(tx, wallet, sum - fee -
|
24
|
+
fill_change_tpc(tx, wallet, sum - fee - FUNDING_TX_AMOUNT)
|
22
25
|
wallet.internal_wallet.sign_tx(tx)
|
23
26
|
end
|
24
27
|
|
25
|
-
def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:,
|
28
|
+
def create_issue_tx_for_reissuable_token(funding_tx:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
26
29
|
tx = Tapyrus::Tx.new
|
27
30
|
|
28
31
|
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
@@ -34,7 +37,7 @@ module Glueby
|
|
34
37
|
receiver_colored_script = receiver_script.add_color(color_id)
|
35
38
|
tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
|
36
39
|
|
37
|
-
fee =
|
40
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
38
41
|
fill_change_tpc(tx, issuer, output.value - fee)
|
39
42
|
prev_txs = [{
|
40
43
|
txid: funding_tx.txid,
|
@@ -45,18 +48,18 @@ module Glueby
|
|
45
48
|
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
46
49
|
end
|
47
50
|
|
48
|
-
def create_issue_tx_for_non_reissuable_token(issuer:, amount:,
|
49
|
-
create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NON_REISSUABLE, issuer: issuer, amount: amount,
|
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)
|
50
53
|
end
|
51
54
|
|
52
|
-
def create_issue_tx_for_nft_token(issuer:,
|
53
|
-
create_issue_tx_from_out_point(token_type: Tapyrus::Color::TokenTypes::NFT, issuer: issuer, amount: 1,
|
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)
|
54
57
|
end
|
55
58
|
|
56
|
-
def create_issue_tx_from_out_point(token_type:, issuer:, amount:,
|
59
|
+
def create_issue_tx_from_out_point(token_type:, issuer:, amount:, fee_estimator: FixedFeeEstimator.new)
|
57
60
|
tx = Tapyrus::Tx.new
|
58
61
|
|
59
|
-
fee =
|
62
|
+
fee = fee_estimator.fee(dummy_issue_tx_from_out_point)
|
60
63
|
sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee)
|
61
64
|
fill_input(tx, outputs)
|
62
65
|
|
@@ -78,7 +81,7 @@ module Glueby
|
|
78
81
|
issuer.internal_wallet.sign_tx(tx)
|
79
82
|
end
|
80
83
|
|
81
|
-
def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:,
|
84
|
+
def create_reissue_tx(funding_tx:, issuer:, amount:, color_id:, fee_estimator: FixedFeeEstimator.new)
|
82
85
|
tx = Tapyrus::Tx.new
|
83
86
|
|
84
87
|
out_point = Tapyrus::OutPoint.from_txid(funding_tx.txid, 0)
|
@@ -89,7 +92,7 @@ module Glueby
|
|
89
92
|
receiver_colored_script = receiver_script.add_color(color_id)
|
90
93
|
tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_colored_script)
|
91
94
|
|
92
|
-
fee =
|
95
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
93
96
|
fill_change_tpc(tx, issuer, output.value - fee)
|
94
97
|
prev_txs = [{
|
95
98
|
txid: funding_tx.txid,
|
@@ -100,7 +103,7 @@ module Glueby
|
|
100
103
|
issuer.internal_wallet.sign_tx(tx, prev_txs)
|
101
104
|
end
|
102
105
|
|
103
|
-
def create_transfer_tx(color_id:, sender:, receiver_address:, amount:,
|
106
|
+
def create_transfer_tx(color_id:, sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
104
107
|
tx = Tapyrus::Tx.new
|
105
108
|
|
106
109
|
utxos = sender.internal_wallet.list_unspent
|
@@ -113,7 +116,7 @@ module Glueby
|
|
113
116
|
|
114
117
|
fill_change_token(tx, sender, sum_token - amount, color_id)
|
115
118
|
|
116
|
-
fee =
|
119
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
117
120
|
sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee)
|
118
121
|
fill_input(tx, outputs)
|
119
122
|
|
@@ -121,7 +124,7 @@ module Glueby
|
|
121
124
|
sender.internal_wallet.sign_tx(tx)
|
122
125
|
end
|
123
126
|
|
124
|
-
def create_burn_tx(color_id:, sender:, amount: 0,
|
127
|
+
def create_burn_tx(color_id:, sender:, amount: 0, fee_estimator: FixedFeeEstimator.new)
|
125
128
|
tx = Tapyrus::Tx.new
|
126
129
|
|
127
130
|
utxos = sender.internal_wallet.list_unspent
|
@@ -130,7 +133,7 @@ module Glueby
|
|
130
133
|
|
131
134
|
fill_change_token(tx, sender, sum_token - amount, color_id) if amount.positive?
|
132
135
|
|
133
|
-
fee =
|
136
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
134
137
|
|
135
138
|
dust = 600 # in case that the wallet has output which has just fee amount.
|
136
139
|
sum_tpc, outputs = sender.internal_wallet.collect_uncolored_outputs(fee + dust)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Glueby
|
2
|
+
class FeeProvider
|
3
|
+
|
4
|
+
autoload :Tasks, 'glueby/fee_provider/tasks'
|
5
|
+
|
6
|
+
class NoUtxosInUtxoPool < StandardError; end
|
7
|
+
|
8
|
+
WALLET_ID = 'FEE_PROVIDER_WALLET'
|
9
|
+
DEFAULT_FIXED_FEE = 1000
|
10
|
+
DEFAULT_UTXO_POOL_SIZE = 20
|
11
|
+
|
12
|
+
attr_reader :fixed_fee, :utxo_pool_size, :wallet,
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_reader :config
|
16
|
+
|
17
|
+
# @param [Hash] config
|
18
|
+
# @option config [Integer] :fixed_fee The amount of fee which FeeProvider would provide to the txs
|
19
|
+
# @option opts [Integer] :utxo_pool_size The number of UTXOs in UTXO pool that is managed by FeeProvider
|
20
|
+
def configure(config)
|
21
|
+
@config = config
|
22
|
+
end
|
23
|
+
|
24
|
+
def provide(tx)
|
25
|
+
new.provide(tx)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@wallet = begin
|
31
|
+
Internal::Wallet.load(WALLET_ID)
|
32
|
+
rescue Internal::Wallet::Errors::WalletNotFound => _
|
33
|
+
Internal::Wallet.create(WALLET_ID)
|
34
|
+
end
|
35
|
+
|
36
|
+
@fixed_fee = (FeeProvider.config && FeeProvider.config[:fixed_fee]) || DEFAULT_FIXED_FEE
|
37
|
+
@utxo_pool_size = (FeeProvider.config && FeeProvider.config[:utxo_pool_size]) || DEFAULT_UTXO_POOL_SIZE
|
38
|
+
end
|
39
|
+
|
40
|
+
# Provide an input for fee to the tx.
|
41
|
+
# @param [Tapyrus::Tx] tx - The tx that is provided fee as a input. It should be signed with ANYONECANPAY flag.
|
42
|
+
# @return [Tapyrus::Tx]
|
43
|
+
# @raise [ArgumentError] If the signatures that the tx inputs has don't have ANYONECANPAY flag.
|
44
|
+
# @raise [Glueby::FeeProvider::NoUtxosInUtxoPool] If there are no UTXOs for paying fee in FeeProvider's UTXO pool
|
45
|
+
def provide(tx)
|
46
|
+
tx.inputs.each do |txin|
|
47
|
+
sig = get_signature(txin.script_sig)
|
48
|
+
unless sig[-1].unpack1('C') & Tapyrus::SIGHASH_TYPE[:anyonecanpay] == Tapyrus::SIGHASH_TYPE[:anyonecanpay]
|
49
|
+
raise ArgumentError, 'All the signatures that the tx inputs has should have ANYONECANPAY flag.'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
utxo = utxo_for_fee
|
54
|
+
out_point = Tapyrus::OutPoint.new(utxo[:txid].rhex, utxo[:vout])
|
55
|
+
tx.inputs << Tapyrus::TxIn.new(out_point: out_point)
|
56
|
+
|
57
|
+
wallet.sign_tx(tx, for_fee_provider_input: true)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def utxo_for_fee
|
63
|
+
utxo = wallet.list_unspent.select { |o| !o[:color_id] && o[:amount] == fixed_fee }.sample
|
64
|
+
raise NoUtxosInUtxoPool, 'No UTXOs in Fee Provider UTXO pool. UTXOs should be created with "glueby:fee_provider:manage_utxo_pool" rake task' unless utxo
|
65
|
+
utxo
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get Signature from P2PKH or CP2PKH script sig
|
69
|
+
def get_signature(script_sig)
|
70
|
+
script_sig.chunks.first.pushed_data
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|