glueby 0.8.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2f53ca4e5784495dc9ddc7669bc021985614b3719205247c42005ec5d12a5c4
4
- data.tar.gz: d5bb0540f3db7cf5ccdb96d4487ff627c4090f69e8719f746badae31689f864d
3
+ metadata.gz: 53718ece4b629ff6474c139b047e08c4803739cb992b61aa4f49c10ca96bc90f
4
+ data.tar.gz: 774199828793ab8c00c3b5d3a501ffa5e265395e7e036407d7ef0edd7f798998
5
5
  SHA512:
6
- metadata.gz: 037ac4f566439facb0971a34c086bb128afd4b00566b7ab0f9013d4b4c357082c88a620eb8262221a245586429387228811e940077868b254b1ec82d155de8ab
7
- data.tar.gz: 9a031d6505750f550abe9948ff977cd01f4fbcad8ab9b733f0b322101cc77d9856c6d2e3f633e61c9e49b4657dc7f286db1abdd4df0511cedf96dba9c27a2d3d
6
+ metadata.gz: 89cb7a6e1a9573bd840811bf75da2d83f9a77eaf1d265e0ba617093e788eeb785f23d80ea67e934714f2773565d26065a4a3daa56bc8d544cc13d2bbd66f3049
7
+ data.tar.gz: d1616df8862ede1f1046119f321b22faf8dd7f6193d4a14ca7a38388d60c6f9e5ac97408480b4c38c2d817036424aeba9f2c50574c655b7ac7f9f3e0522a0cac
data/README.md CHANGED
@@ -457,6 +457,11 @@ Glueby.configure do |config|
457
457
  end
458
458
  ```
459
459
 
460
+ ## Error handling
461
+
462
+ Glueby has base error classes like `Glueby::Error` and `Glueby::ArgumentError`.
463
+ `Glueby::Error` is the base class for the all errors that are raises in the glueby.
464
+ `Glueby::ArgumentError` is the error class for argument errors in public contracts. This notifies the arguments is something wrong to glueby library user-side.
460
465
 
461
466
  ## Development
462
467
 
data/glueby.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
27
  spec.require_paths = ["lib"]
28
28
 
29
- spec.add_runtime_dependency 'tapyrus', '>= 0.2.9'
29
+ spec.add_runtime_dependency 'tapyrus', '>= 0.2.13'
30
30
  spec.add_runtime_dependency 'activerecord', '~> 6.1.3'
31
31
  spec.add_development_dependency 'sqlite3'
32
32
  spec.add_development_dependency 'rails', '~> 6.1.3'
@@ -9,6 +9,10 @@ class CreateTimestamp < ActiveRecord::Migration<%= migration_version %>
9
9
  t.integer :timestamp_type, null: false, default: 0
10
10
  t.string :p2c_address
11
11
  t.string :payment_base
12
+ t.bigint :prev_id
13
+ t.boolean :latest, null: false, default: true
12
14
  end
15
+
16
+ add_index :glueby_timestamps, [:prev_id], unique: true
13
17
  end
14
18
  end
@@ -12,22 +12,91 @@ module Glueby
12
12
  find_by(info_key: "use_only_finalized_utxo")&.int_value != 0
13
13
  end
14
14
 
15
+
16
+ # Set use_only_finalized_utxo
17
+ # @param [Boolean] status status of whether to use only finalized utxo
18
+ def self.set_use_only_finalized_utxo(status)
19
+ current = find_by(info_key: "use_only_finalized_utxo")
20
+ if current
21
+ current.update!(info_value: boolean_to_string(status))
22
+ else
23
+ create!(
24
+ info_key: "use_only_finalized_utxo",
25
+ info_value: boolean_to_string(status)
26
+ )
27
+ end
28
+ end
29
+
15
30
  # Return default value of the utxo provider
16
31
  # @return [Integer] default value of utxo provider
17
32
  def self.utxo_provider_default_value
18
33
  find_by(info_key: "utxo_provider_default_value")&.int_value
19
34
  end
20
35
 
36
+ # Set utxo_provider_default_value
37
+ # @param [Integer] value default value for utxo provider
38
+ def self.set_utxo_provider_default_value(value)
39
+ current = find_by(info_key: "utxo_provider_default_value")
40
+ if current
41
+ current.update!(info_value: value)
42
+ else
43
+ create!(
44
+ info_key: "utxo_provider_default_value",
45
+ info_value: value
46
+ )
47
+ end
48
+ end
49
+
21
50
  # Return pool size of the utxo provider
22
51
  # @return [Integer] pool size of utxo provider
23
52
  def self.utxo_provider_pool_size
24
53
  find_by(info_key: "utxo_provider_pool_size")&.int_value
25
54
  end
26
55
 
56
+ # Set utxo_provider_pool_size
57
+ # @param [Integer] size pool size of the utxo provider
58
+ def self.set_utxo_provider_pool_size(size)
59
+ current = find_by(info_key: "utxo_provider_pool_size")
60
+ if current
61
+ current.update!(info_value: size)
62
+ else
63
+ create!(
64
+ info_key: "utxo_provider_pool_size",
65
+ info_value: size
66
+ )
67
+ end
68
+ end
69
+
70
+ # If return timestamp is to be executed immediately
71
+ # @return [Boolean] true status of broadcast_on_background
72
+ def self.broadcast_on_background?
73
+ find_by(info_key: "broadcast_on_background")&.int_value != 0
74
+ end
75
+
76
+ # Set the status of broadcast_on_background
77
+ # @param [Boolean] status status of broadcast_on_background
78
+ def self.set_broadcast_on_background(status)
79
+ current = find_by(info_key: "broadcast_on_background")
80
+ if current
81
+ current.update!(info_value: boolean_to_string(status))
82
+ else
83
+ create!(
84
+ info_key: "broadcast_on_background",
85
+ info_value: boolean_to_string(status)
86
+ )
87
+ end
88
+ end
89
+
27
90
  def int_value
28
91
  info_value.to_i
29
92
  end
30
93
 
94
+ private
95
+
96
+ def self.boolean_to_string(status)
97
+ status ? "1" : "0"
98
+ end
99
+
31
100
  end
32
101
  end
33
102
  end
@@ -15,7 +15,7 @@ module Glueby
15
15
  alias_method :use_utxo_provider?, :use_utxo_provider
16
16
 
17
17
  module Errors
18
- class InvalidConfiguration < StandardError; end
18
+ class InvalidConfiguration < Error; end
19
19
  end
20
20
 
21
21
  def initialize
@@ -3,53 +3,179 @@ module Glueby
3
3
  module AR
4
4
  class Timestamp < ::ActiveRecord::Base
5
5
  include Glueby::GluebyLogger
6
- include Glueby::Contract::Timestamp::Util
7
- include Glueby::Contract::TxBuilder
8
6
 
9
7
  enum status: { init: 0, unconfirmed: 1, confirmed: 2 }
10
8
  enum timestamp_type: { simple: 0, trackable: 1 }
11
9
 
10
+ attr_reader :tx
11
+
12
+ belongs_to :prev, class_name: 'Glueby::Contract::AR::Timestamp'
13
+
14
+ class << self
15
+ def digest_content(content, digest)
16
+ case digest&.downcase
17
+ when :sha256
18
+ Tapyrus.sha256(content).bth
19
+ when :double_sha256
20
+ Tapyrus.double_sha256(content).bth
21
+ when :none
22
+ content
23
+ else
24
+ raise Glueby::Contract::Errors::UnsupportedDigestType
25
+ end
26
+ end
27
+ end
28
+
12
29
  # @param [Hash] attributes attributes which consist of:
13
30
  # - wallet_id
14
31
  # - content
15
32
  # - prefix(optional)
16
33
  # - timestamp_type(optional)
34
+ # @raise [Glueby::ArgumentError] If the timestamp_type is not in :simple or :trackable
17
35
  def initialize(attributes = nil)
18
- @content_hash = Tapyrus.sha256(attributes[:content]).bth
19
- super(wallet_id: attributes[:wallet_id], content_hash: @content_hash,
20
- prefix: attributes[:prefix] ? attributes[:prefix] : '', status: :init, timestamp_type: attributes[:timestamp_type] || :simple)
36
+ # Set content_hash from :content attribute
37
+ content_hash = Timestamp.digest_content(attributes[:content], attributes[:digest] || :sha256)
38
+ super(
39
+ wallet_id: attributes[:wallet_id],
40
+ content_hash: content_hash,
41
+ prefix: attributes[:prefix] ? attributes[:prefix] : '',
42
+ status: :init,
43
+ timestamp_type: attributes[:timestamp_type] || :simple,
44
+ prev_id: attributes[:prev_id]
45
+ )
46
+ rescue ::ArgumentError => e
47
+ raise Glueby::ArgumentError, e.message
21
48
  end
22
49
 
23
50
  # Return true if timestamp type is 'trackable' and output in timestamp transaction has not been spent yet, otherwise return false.
24
- def latest
25
- trackable?
51
+ def latest?
52
+ trackable? && attributes['latest']
53
+ end
54
+ alias_method :latest, :latest?
55
+
56
+ # Returns a UTXO that corresponds to the timestamp
57
+ # @return [Hash] UTXO
58
+ # - [String] script_pubkey A script pubkey hex string
59
+ # - [String] txid A txid
60
+ # - [Integer] vout An index of the tx
61
+ # - [Integer] amount A value of
62
+ def utxo
63
+ {
64
+ script_pubkey: Tapyrus::Script.parse_from_addr(p2c_address).to_hex,
65
+ txid: txid,
66
+ vout: Contract::Timestamp::TxBuilder::PAY_TO_CONTRACT_INPUT_INDEX,
67
+ amount: Glueby::Contract::Timestamp::P2C_DEFAULT_VALUE
68
+ }
26
69
  end
27
70
 
28
71
  # Broadcast and save timestamp
29
72
  # @param [Glueby::Contract::FixedFeeEstimator] fee_estimator
73
+ # @param [Glueby::UtxoProvider] utxo_provider
30
74
  # @return true if tapyrus transactions were broadcasted and the timestamp was updated successfully, otherwise false.
31
- def save_with_broadcast(fee_estimator: Glueby::Contract::FixedFeeEstimator.new)
32
- utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
33
- wallet = Glueby::Wallet.load(wallet_id)
34
- funding_tx, tx, p2c_address, payment_base = create_txs(wallet, prefix, content_hash, fee_estimator, utxo_provider, type: timestamp_type.to_sym)
75
+ def save_with_broadcast(fee_estimator: Glueby::Contract::FixedFeeEstimator.new, utxo_provider: nil)
76
+ save_with_broadcast!(fee_estimator: fee_estimator, utxo_provider: utxo_provider)
77
+ rescue Errors::FailedToBroadcast, Errors::PrevTimestampNotFound, Errors::PrevTimestampIsNotTrackable => e
78
+ logger.error("failed to broadcast (id=#{id}, reason=#{e.message})")
79
+ false
80
+ end
81
+
82
+ # Broadcast and save timestamp, and it raises errors
83
+ # @param [Glueby::Contract::FixedFeeEstimator] fee_estimator
84
+ # @param [Glueby::UtxoProvider] utxo_provider
85
+ # @return true if tapyrus transactions were broadcasted and the timestamp was updated successfully
86
+ # @raise [Glueby::Contract::Errors::FailedToBroadcast] If the broadcasting is failure
87
+ # @raise [Glueby::Contract::Errors::PrevTimestampNotFound] If it is not available that the timestamp record which correspond with the prev_id attribute
88
+ # @raise [Glueby::Contract::Errors::PrevTimestampIsNotTrackable] If the timestamp record by prev_id is not trackable
89
+ def save_with_broadcast!(fee_estimator: Glueby::Contract::FixedFeeEstimator.new, utxo_provider: nil)
90
+ utxo_provider = Glueby::UtxoProvider.new if !utxo_provider && Glueby.configuration.use_utxo_provider?
91
+
92
+ funding_tx, tx, p2c_address, payment_base = create_txs(fee_estimator, utxo_provider)
93
+
35
94
  if funding_tx
36
95
  ::ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
37
96
  wallet.internal_wallet.broadcast(funding_tx)
38
- logger.info("funding tx was broadcasted(id=#{id}, funding_tx.txid=#{funding_tx.txid})")
39
97
  end
98
+ logger.info("funding tx was broadcasted(id=#{id}, funding_tx.txid=#{funding_tx.txid})")
40
99
  end
41
100
  ::ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
42
101
  wallet.internal_wallet.broadcast(tx) do |tx|
43
102
  assign_attributes(txid: tx.txid, status: :unconfirmed, p2c_address: p2c_address, payment_base: payment_base)
103
+ @tx = tx
44
104
  save!
105
+
106
+ if update_trackable?
107
+ prev.latest = false
108
+ prev.save!
109
+ end
45
110
  end
46
- logger.info("timestamp tx was broadcasted (id=#{id}, txid=#{tx.txid})")
47
111
  end
112
+ logger.info("timestamp tx was broadcasted (id=#{id}, txid=#{tx.txid})")
48
113
  true
49
- rescue => e
50
- logger.error("failed to broadcast (id=#{id}, reason=#{e.message})")
114
+ rescue ActiveRecord::RecordInvalid,
115
+ Tapyrus::RPC::Error,
116
+ Internal::Wallet::Errors::WalletAlreadyLoaded,
117
+ Internal::Wallet::Errors::WalletNotFound,
118
+ Errors::InsufficientFunds => e
51
119
  errors.add(:base, "failed to broadcast (id=#{id}, reason=#{e.message})")
52
- false
120
+ raise Errors::FailedToBroadcast, "failed to broadcast (id=#{id}, reason=#{e.message})"
121
+ end
122
+
123
+ private
124
+
125
+ def wallet
126
+ @wallet ||= Glueby::Wallet.load(wallet_id)
127
+ end
128
+
129
+ def create_txs(fee_estimator, utxo_provider)
130
+ builder = builder_class.new(wallet, fee_estimator)
131
+
132
+ if builder.instance_of?(Contract::Timestamp::TxBuilder::UpdatingTrackable)
133
+ unless prev
134
+ message = "The previous timestamp(id: #{prev_id}) not found."
135
+ errors.add(:prev_id, message)
136
+ raise Errors::PrevTimestampNotFound, message
137
+ end
138
+
139
+ unless prev.trackable?
140
+ message = "The previous timestamp(id: #{prev_id}) type must be trackable"
141
+ errors.add(:prev_id, message)
142
+ raise Errors::PrevTimestampIsNotTrackable, message
143
+ end
144
+
145
+ builder.set_prev_timestamp_info(
146
+ timestamp_utxo: prev.utxo,
147
+ payment_base: prev.payment_base,
148
+ prefix: prev.prefix,
149
+ data: prev.content_hash
150
+ )
151
+ end
152
+
153
+ tx = builder.set_data(prefix, content_hash)
154
+ .set_inputs(utxo_provider)
155
+ .build
156
+
157
+ if builder.instance_of?(Contract::Timestamp::TxBuilder::Simple)
158
+ [builder.funding_tx, tx, nil, nil]
159
+ else
160
+ [builder.funding_tx, tx, builder.p2c_address, builder.payment_base]
161
+ end
162
+ end
163
+
164
+ def builder_class
165
+ case timestamp_type.to_sym
166
+ when :simple
167
+ Contract::Timestamp::TxBuilder::Simple
168
+ when :trackable
169
+ if prev_id
170
+ Contract::Timestamp::TxBuilder::UpdatingTrackable
171
+ else
172
+ Contract::Timestamp::TxBuilder::Trackable
173
+ end
174
+ end
175
+ end
176
+
177
+ def update_trackable?
178
+ trackable? && prev_id
53
179
  end
54
180
  end
55
181
  end
@@ -1,16 +1,22 @@
1
1
  module Glueby
2
2
  module Contract
3
3
  module Errors
4
- class InsufficientFunds < StandardError; end
5
- class InsufficientTokens < StandardError; end
6
- class InvalidAmount < StandardError; end
7
- class InvalidSplit < StandardError; end
8
- class InvalidTokenType < StandardError; end
9
- class InvalidTimestampType < StandardError; end
10
- class TxAlreadyBroadcasted < StandardError; end
11
- class UnsupportedTokenType < StandardError; end
12
- class UnknownScriptPubkey < StandardError; end
13
- class UnsupportedDigestType < StandardError; end
4
+ class InsufficientFunds < Error; end
5
+ class InsufficientTokens < Error; end
6
+ class TxAlreadyBroadcasted < Error; end
7
+ class FailedToBroadcast < Error; end
8
+
9
+ # Argument Errors
10
+ class ArgumentError < ArgumentError; end
11
+ class InvalidAmount < ArgumentError; end
12
+ class InvalidSplit < ArgumentError; end
13
+ class InvalidTokenType < ArgumentError; end
14
+ class InvalidTimestampType < ArgumentError; end
15
+ class UnsupportedTokenType < ArgumentError; end
16
+ class UnknownScriptPubkey < ArgumentError; end
17
+ class UnsupportedDigestType < ArgumentError; end
18
+ class PrevTimestampNotFound < ArgumentError; end
19
+ class PrevTimestampIsNotTrackable < ArgumentError; end
14
20
  end
15
21
  end
16
22
  end
@@ -0,0 +1,97 @@
1
+ module Glueby
2
+ module Contract
3
+ class Timestamp
4
+ module TxBuilder
5
+ # The simple Timestamp method
6
+ class Simple
7
+ include Glueby::Contract::TxBuilder
8
+
9
+ attr_reader :funding_tx
10
+
11
+ def initialize(wallet, fee_estimator)
12
+ @wallet = wallet
13
+ @fee_estimator = fee_estimator
14
+
15
+ @txb = Tapyrus::TxBuilder.new
16
+ @prev_txs = []
17
+ end
18
+
19
+ def build
20
+ @txb.fee(fee).change_address(@wallet.internal_wallet.change_address)
21
+ sign_tx
22
+ end
23
+
24
+ def set_data(prefix, data)
25
+ @prefix = prefix
26
+ @data = data
27
+
28
+ @txb.data(prefix + data)
29
+ self
30
+ end
31
+
32
+ def set_inputs(utxo_provider)
33
+ if utxo_provider
34
+ script_pubkey = Tapyrus::Script.parse_from_addr(@wallet.internal_wallet.receive_address)
35
+ @funding_tx, index = utxo_provider.get_utxo(script_pubkey, fee)
36
+
37
+ utxo = {
38
+ script_pubkey: @funding_tx.outputs[index].script_pubkey.to_hex,
39
+ txid: @funding_tx.txid,
40
+ vout: index,
41
+ amount: funding_tx.outputs[index].value
42
+ }
43
+
44
+ @txb.add_utxo(to_tapyrusrb_utxo_hash(utxo))
45
+ @prev_txs << to_sign_tx_utxo_hash(utxo)
46
+ else
47
+ _, outputs = @wallet.internal_wallet.collect_uncolored_outputs(fee)
48
+ outputs.each do |utxo|
49
+ @txb.add_utxo(to_tapyrusrb_utxo_hash(utxo))
50
+ end
51
+ end
52
+ self
53
+ end
54
+
55
+ private
56
+
57
+ def fee
58
+ @fee ||= @fee_estimator.fee(dummy_tx(@txb.build))
59
+ end
60
+
61
+ def sign_tx
62
+ # General signing process skips signing to p2c inputs because no key of the p2c address in the wallet.
63
+ @wallet.internal_wallet.sign_tx(@txb.build, @prev_txs)
64
+ end
65
+
66
+ # @param utxo
67
+ # @option utxo [String] :txid The txid
68
+ # @option utxo [Integer] :vout The index of the output in the tx
69
+ # @option utxo [Integer] :amount The value of the output
70
+ # @option utxo [String] :script_pubkey The hex string of the script pubkey
71
+ def to_tapyrusrb_utxo_hash(utxo)
72
+ {
73
+ script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
74
+ txid: utxo[:txid],
75
+ index: utxo[:vout],
76
+ value: utxo[:amount]
77
+ }
78
+ end
79
+
80
+ # @param utxo
81
+ # @option utxo [String] :txid The txid
82
+ # @option utxo [Integer] :vout The index of the output in the tx
83
+ # @option utxo [Integer] :amount The value of the output
84
+ # @option utxo [String] :script_pubkey The hex string of the script pubkey
85
+ def to_sign_tx_utxo_hash(utxo)
86
+ {
87
+ scriptPubKey: utxo[:script_pubkey],
88
+ txid: utxo[:txid],
89
+ vout: utxo[:vout],
90
+ amount: utxo[:amount]
91
+ }
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,23 @@
1
+ module Glueby
2
+ module Contract
3
+ class Timestamp
4
+ module TxBuilder
5
+ class Trackable < Simple
6
+ attr_reader :p2c_address, :payment_base
7
+
8
+ # @override
9
+ def set_data(prefix, data)
10
+ @prefix = prefix
11
+ @data = data
12
+
13
+ # Create a new trackable timestamp
14
+ @p2c_address, @payment_base = @wallet.internal_wallet
15
+ .create_pay_to_contract_address([prefix, data].join)
16
+ @txb.pay(p2c_address, P2C_DEFAULT_VALUE)
17
+ self
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module Glueby
2
+ module Contract
3
+ class Timestamp
4
+ module TxBuilder
5
+ class UpdatingTrackable < Trackable
6
+ def set_prev_timestamp_info(timestamp_utxo:, payment_base:, prefix:, data:)
7
+ @prev_timestamp_utxo = timestamp_utxo
8
+ @prev_payment_base = payment_base
9
+ @prev_prefix = prefix
10
+ @prev_data = data
11
+ @txb.add_utxo(to_tapyrusrb_utxo_hash(@prev_timestamp_utxo))
12
+ end
13
+
14
+ def sign_tx
15
+ tx = super
16
+ # Generates signature for the remain p2c input.
17
+ @wallet.internal_wallet.sign_to_pay_to_contract_address(
18
+ tx,
19
+ @prev_timestamp_utxo,
20
+ @prev_payment_base,
21
+ [@prev_prefix, @prev_data].join
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ module Glueby
2
+ module Contract
3
+ class Timestamp
4
+ module TxBuilder
5
+ autoload :Simple, 'glueby/contract/timestamp/tx_builder/simple'
6
+ autoload :Trackable, 'glueby/contract/timestamp/tx_builder/trackable'
7
+ autoload :UpdatingTrackable, 'glueby/contract/timestamp/tx_builder/updating_trackable'
8
+
9
+ # A transaction input to update trackable timestamp must placed at this index in the tx inputs.
10
+ PAY_TO_CONTRACT_INPUT_INDEX = 0
11
+ end
12
+ end
13
+ end
14
+ end
@@ -8,87 +8,16 @@ module Glueby
8
8
  #
9
9
  # Storing timestamp transaction to the blockchain enables everyone to verify that the data existed at that time and a user signed it.
10
10
  class Timestamp
11
- include Glueby::Contract::TxBuilder
12
-
13
11
  P2C_DEFAULT_VALUE = 1_000
14
12
 
15
13
  autoload :Syncer, 'glueby/contract/timestamp/syncer'
16
-
17
- module Util
18
- include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
19
- module_function
20
-
21
- # @param [Glueby::Wallet] wallet
22
- # @param [Array] contents The data to be used for generating pay-to-contract address
23
- # @return [pay-to-contract address, public key used for generating address]
24
- def create_pay_to_contract_address(wallet, contents: nil)
25
- pubkey = wallet.internal_wallet.create_pubkey.pubkey
26
- # Calculate P + H(P || contents)G
27
- group = ECDSA::Group::Secp256k1
28
- p = Tapyrus::Key.new(pubkey: pubkey).to_point # P
29
- commitment = Tapyrus.sha256(p.to_hex(true).htb + contents.join).bth.to_i(16) % group.order # H(P || contents)
30
- point = p + group.generator.multiply_by_scalar(commitment) # P + H(P || contents)G
31
- [Tapyrus::Key.new(pubkey: point.to_hex(true)).to_p2pkh, pubkey] # [p2c address, P]
32
- end
33
-
34
- def create_txs(wallet, prefix, data, fee_estimator, utxo_provider, type: :simple)
35
- txb = Tapyrus::TxBuilder.new
36
- if type == :simple
37
- txb.data(prefix + data)
38
- elsif type == :trackable
39
- p2c_address, payment_base = create_pay_to_contract_address(wallet, contents: [prefix, data])
40
- txb.pay(p2c_address, P2C_DEFAULT_VALUE)
41
- end
42
-
43
- fee = fee_estimator.fee(dummy_tx(txb.build))
44
- if utxo_provider
45
- script_pubkey = Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
46
- funding_tx, index = utxo_provider.get_utxo(script_pubkey, fee)
47
- txb.add_utxo({
48
- script_pubkey: funding_tx.outputs[index].script_pubkey,
49
- txid: funding_tx.txid,
50
- index: index,
51
- value: funding_tx.outputs[index].value
52
- })
53
- else
54
- sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
55
- outputs.each do |utxo|
56
- txb.add_utxo({
57
- script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
58
- txid: utxo[:txid],
59
- index: utxo[:vout],
60
- value: utxo[:amount]
61
- })
62
- end
63
- end
64
-
65
- prev_txs = if funding_tx
66
- output = funding_tx.outputs.first
67
- [{
68
- txid: funding_tx.txid,
69
- vout: 0,
70
- scriptPubKey: output.script_pubkey.to_hex,
71
- amount: output.value
72
- }]
73
- else
74
- []
75
- end
76
-
77
- txb.fee(fee).change_address(wallet.internal_wallet.change_address)
78
- [funding_tx, wallet.internal_wallet.sign_tx(txb.build, prev_txs), p2c_address, payment_base]
79
- end
80
-
81
- def get_transaction(tx)
82
- Glueby::Internal::RPC.client.getrawtransaction(tx.txid, 1)
83
- end
84
- end
85
- include Glueby::Contract::Timestamp::Util
14
+ autoload :TxBuilder, 'glueby/contract/timestamp/tx_builder'
86
15
 
87
16
  attr_reader :tx, :txid
88
-
89
17
  # p2c_address and payment_base is used in `trackable` type
90
18
  attr_reader :p2c_address, :payment_base
91
19
 
20
+ # @param [Gleuby::Wallet] wallet The wallet that is sender of the timestamp transaction.
92
21
  # @param [String] content Data to be hashed and stored in blockchain.
93
22
  # @param [String] prefix prefix of op_return data
94
23
  # @param [Glueby::Contract::FeeEstimator] fee_estimator
@@ -99,6 +28,7 @@ module Glueby
99
28
  # @param [Symbol] timestamp_type
100
29
  # - :simple
101
30
  # - :trackable
31
+ # @param [Integer] prev_timestamp_id The id column of glueby_timestamps that will be updated by the timestamp that will be created
102
32
  # @raise [Glueby::Contract::Errors::UnsupportedDigestType] if digest is unsupported
103
33
  # @raise [Glueby::Contract::Errors::InvalidTimestampType] if timestamp_type is unsupported
104
34
  def initialize(
@@ -108,7 +38,8 @@ module Glueby
108
38
  fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
109
39
  digest: :sha256,
110
40
  utxo_provider: nil,
111
- timestamp_type: :simple
41
+ timestamp_type: :simple,
42
+ prev_timestamp_id: nil
112
43
  )
113
44
  @wallet = wallet
114
45
  @content = content
@@ -119,33 +50,46 @@ module Glueby
119
50
  @utxo_provider = utxo_provider
120
51
  raise Glueby::Contract::Errors::InvalidTimestampType, "#{timestamp_type} is invalid type, supported types are :simple, and :trackable." unless [:simple, :trackable].include?(timestamp_type)
121
52
  @timestamp_type = timestamp_type
53
+ @prev_timestamp_id = prev_timestamp_id
122
54
  end
123
55
 
124
56
  # broadcast to Tapyrus Core
125
57
  # @return [String] txid
126
- # @raise [TxAlreadyBroadcasted] if tx has been broadcasted.
127
- # @raise [InsufficientFunds] if result of listunspent is not enough to pay the specified amount
58
+ # @raise [Glueby::Contract::Errors::TxAlreadyBroadcasted] if tx has been broadcasted.
59
+ # @raise [Glueby::Contract::Errors::InsufficientFunds] if result of listunspent is not enough to pay the specified amount
128
60
  def save!
129
- raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
61
+ raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @ar
62
+
63
+ @ar = Glueby::Contract::AR::Timestamp.new(
64
+ wallet_id: @wallet.id,
65
+ prefix: @prefix,
66
+ content: @content,
67
+ timestamp_type: @timestamp_type,
68
+ digest: @digest,
69
+ prev_id: @prev_timestamp_id
70
+ )
71
+ @ar.save_with_broadcast!(fee_estimator: @fee_estimator, utxo_provider: @utxo_provider)
72
+ @ar.txid
73
+ end
130
74
 
131
- funding_tx, @tx, @p2c_address, @payment_base = create_txs(@wallet, @prefix, digest_content, @fee_estimator, @utxo_provider, type: @timestamp_type)
132
- @wallet.internal_wallet.broadcast(funding_tx) if funding_tx
133
- @txid = @wallet.internal_wallet.broadcast(@tx)
75
+ def txid
76
+ raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
77
+ @ar.txid
134
78
  end
135
79
 
136
- private
80
+ def tx
81
+ raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
82
+ @ar.tx
83
+ end
84
+
85
+ def p2c_address
86
+ raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
87
+ @ar.p2c_address
88
+ end
137
89
 
138
- def digest_content
139
- case @digest&.downcase
140
- when :sha256
141
- Tapyrus.sha256(@content)
142
- when :double_sha256
143
- Tapyrus.double_sha256(@content)
144
- when :none
145
- @content
146
- else
147
- raise Glueby::Contract::Errors::UnsupportedDigestType
148
- end
90
+ def payment_base
91
+ raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
92
+ @ar.payment_base
149
93
  end
150
94
  end
151
95
  end
@@ -91,11 +91,14 @@ module Glueby
91
91
  script_pubkey = funding_tx.outputs.first.script_pubkey
92
92
  color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
93
93
 
94
+ ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
95
+ funding_tx = issuer.internal_wallet.broadcast(funding_tx)
96
+ end
97
+
94
98
  ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
95
99
  # Store the script_pubkey for reissue the token.
96
100
  Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
97
101
 
98
- funding_tx = issuer.internal_wallet.broadcast(funding_tx)
99
102
  tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount, split: split)
100
103
  tx = issuer.internal_wallet.broadcast(tx)
101
104
  [[funding_tx, tx], color_id]
@@ -8,6 +8,5 @@ module Glueby
8
8
  autoload :Token, 'glueby/contract/token'
9
9
  autoload :TxBuilder, 'glueby/contract/tx_builder'
10
10
  autoload :AR, 'glueby/contract/active_record'
11
- autoload :Wallet, 'glueby/contract/wallet'
12
11
  end
13
12
  end
@@ -3,7 +3,7 @@ module Glueby
3
3
 
4
4
  autoload :Tasks, 'glueby/fee_provider/tasks'
5
5
 
6
- class NoUtxosInUtxoPool < StandardError; end
6
+ class NoUtxosInUtxoPool < Error; end
7
7
 
8
8
  WALLET_ID = 'FEE_PROVIDER_WALLET'
9
9
  DEFAULT_FIXED_FEE = 1000
@@ -42,7 +42,7 @@ module Glueby
42
42
  # Provide an input for fee to the tx.
43
43
  # @param [Tapyrus::Tx] tx - The tx that is provided fee as a input. It should be signed with ANYONECANPAY flag.
44
44
  # @return [Tapyrus::Tx]
45
- # @raise [ArgumentError] If the signatures that the tx inputs has don't have ANYONECANPAY flag.
45
+ # @raise [Glueby::ArgumentError] If the signatures that the tx inputs has don't have ANYONECANPAY flag.
46
46
  # @raise [Glueby::FeeProvider::NoUtxosInUtxoPool] If there are no UTXOs for paying fee in FeeProvider's UTXO pool
47
47
  def provide(tx)
48
48
  tx.inputs.each do |txin|
@@ -148,6 +148,31 @@ module Glueby
148
148
  def get_addresses(wallet_id, label = nil)
149
149
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
150
150
  end
151
+
152
+ # Create and returns pay to contract address
153
+ # @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method.
154
+ # @param [String] contents - The data to be used for generating pay-to-contract address
155
+ # @return [String] pay to contract P2PKH address
156
+ def create_pay_to_contract_address(wallet_id, contents)
157
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
158
+ end
159
+
160
+ # Sign to the pay to contract input
161
+ # @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method.
162
+ # @param [Tapyrus::Tx] tx - The tx that has pay to contract input in the inputs list
163
+ # @param [Hash] utxo - The utxo that indicates pay to contract output to be signed
164
+ # @option utxo [String] txid - Transaction id
165
+ # @option utxo [Integer] vout - Output index
166
+ # @option utxo [Integer] amount - The amount the output has
167
+ # @option utxo [String] script_pubkey - The script_pubkey hex string
168
+ # @param [String] payment_base - The public key that is used to generate pay to contract public key
169
+ # @param [String] contents - The data to be used for generating pay-to-contract address
170
+ # @param [Integer] sighashtype - The sighash flag for each signature that would be produced here.
171
+ # @return [Tapyrus::Tx]
172
+ # @raise [Glueby::Internal::Wallet::Errors::InvalidSighashType] when the specified sighashtype is invalid
173
+ def sign_to_pay_to_contract_address(wallet_id, tx, utxo, payment_base, contents, sighashtype: Tapyrus::SIGHASH_TYPE[:all])
174
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
175
+ end
151
176
  end
152
177
  end
153
178
  end
@@ -153,6 +153,51 @@ module Glueby
153
153
  keys = keys.where(label: label) if label
154
154
  keys.map(&:address)
155
155
  end
156
+
157
+ def create_pay_to_contract_address(wallet_id, contents)
158
+ # Calculate P + H(P || contents)G
159
+ group = ECDSA::Group::Secp256k1
160
+ pubkey = create_pubkey(wallet_id)
161
+ p = pubkey.to_point # P
162
+ commitment = create_pay_to_contract_commitment(pubkey, contents)
163
+ point = p + group.generator.multiply_by_scalar(commitment) # P + H(P || contents)G
164
+ [Tapyrus::Key.new(pubkey: point.to_hex(true)).to_p2pkh, pubkey.pubkey] # [p2c address, P]
165
+ end
166
+
167
+ def sign_to_pay_to_contract_address(wallet_id, tx, utxo, payment_base, contents)
168
+ key = create_pay_to_contract_private_key(wallet_id, payment_base, contents)
169
+ sighash = tx.sighash_for_input(utxo[:vout], Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb))
170
+
171
+ sig = key.sign(sighash, algo: :schnorr) + [Tapyrus::SIGHASH_TYPE[:all]].pack('C')
172
+ script_sig = Tapyrus::Script.parse_from_payload(Tapyrus::Script.pack_pushdata(sig) + Tapyrus::Script.pack_pushdata(key.pubkey.htb))
173
+ tx.inputs[utxo[:vout]].script_sig = script_sig
174
+ tx
175
+ end
176
+
177
+ private
178
+
179
+ # Calculate commitment = H(P || contents)
180
+ # @param [Tapyrus::Key] pubkey The public key
181
+ # @param [String] contents
182
+ # @return Integer
183
+ def create_pay_to_contract_commitment(pubkey, contents)
184
+ group = ECDSA::Group::Secp256k1
185
+ p = pubkey.to_point # P
186
+ Tapyrus.sha256(p.to_hex(true).htb + contents).bth.to_i(16) % group.order # H(P || contents)
187
+ end
188
+
189
+ # @param [String] wallet_id
190
+ # @param [String] payment_base The public key hex string
191
+ # @param [String] contents
192
+ # @return [Tapyrus::Key] pay to contract private key
193
+ def create_pay_to_contract_private_key(wallet_id, payment_base, contents)
194
+ group = ECDSA::Group::Secp256k1
195
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
196
+ ar_key = wallet.keys.where(public_key: payment_base).first
197
+ key = Tapyrus::Key.new(pubkey: payment_base)
198
+ commitment = create_pay_to_contract_commitment(key, contents)
199
+ Tapyrus::Key.new(priv_key: ((ar_key.private_key.to_i(16) + commitment) % group.order).to_even_length_hex) # K + commitment
200
+ end
156
201
  end
157
202
  end
158
203
  end
@@ -2,12 +2,12 @@ module Glueby
2
2
  module Internal
3
3
  class Wallet
4
4
  module Errors
5
- class ShouldInitializeWalletAdapter < StandardError; end
6
- class WalletUnloaded < StandardError; end
7
- class WalletAlreadyLoaded < StandardError; end
8
- class WalletAlreadyCreated < StandardError; end
9
- class WalletNotFound < StandardError; end
10
- class InvalidSighashType < StandardError; end
5
+ class ShouldInitializeWalletAdapter < Error; end
6
+ class WalletUnloaded < Error; end
7
+ class WalletAlreadyLoaded < Error; end
8
+ class WalletAlreadyCreated < Error; end
9
+ class WalletNotFound < Error; end
10
+ class InvalidSighashType < Error; end
11
11
  end
12
12
  end
13
13
  end
@@ -154,6 +154,14 @@ module Glueby
154
154
  wallet_adapter.get_addresses(id, label)
155
155
  end
156
156
 
157
+ def create_pay_to_contract_address(contents)
158
+ wallet_adapter.create_pay_to_contract_address(id, contents)
159
+ end
160
+
161
+ def sign_to_pay_to_contract_address(tx, utxo, payment_base, contents)
162
+ wallet_adapter.sign_to_pay_to_contract_address(id, tx, utxo, payment_base, contents)
163
+ end
164
+
157
165
  private
158
166
 
159
167
  def wallet_adapter
@@ -30,14 +30,14 @@ module Glueby
30
30
 
31
31
  utxos.each { |utxo| txb.add_utxo(utxo) }
32
32
 
33
- shortage = [utxo_provider.utxo_pool_size - current_utxo_pool_size, 0].max
33
+ shortage = [utxo_provider.utxo_pool_size - utxo_provider.current_utxo_pool_size, 0].max
34
34
  return if shortage == 0
35
35
 
36
36
  added_outputs = 0
37
37
  shortage.times do
38
38
  fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
39
39
  break if (sum - fee) < utxo_provider.default_value
40
- txb.pay(address, utxo_provider.default_value)
40
+ txb.pay(utxo_provider.address, utxo_provider.default_value)
41
41
  sum -= utxo_provider.default_value
42
42
  added_outputs += 1
43
43
  end
@@ -45,11 +45,11 @@ module Glueby
45
45
  return if added_outputs == 0
46
46
 
47
47
  fee = utxo_provider.fee_estimator.fee(dummy_tx(txb.build))
48
- tx = txb.change_address(address)
48
+ tx = txb.change_address(utxo_provider.address)
49
49
  .fee(fee)
50
50
  .build
51
- tx = wallet.sign_tx(tx)
52
- wallet.broadcast(tx)
51
+ tx = utxo_provider.wallet.sign_tx(tx)
52
+ utxo_provider.wallet.broadcast(tx)
53
53
  ensure
54
54
  status
55
55
  end
@@ -58,13 +58,13 @@ module Glueby
58
58
  def status
59
59
  status = :ready
60
60
 
61
- if current_utxo_pool_size < utxo_provider.utxo_pool_size
62
- if tpc_amount < value_to_fill_utxo_pool
61
+ if utxo_provider.current_utxo_pool_size < utxo_provider.utxo_pool_size
62
+ if utxo_provider.tpc_amount < utxo_provider.value_to_fill_utxo_pool
63
63
  status = :insufficient_amount
64
64
  message = <<~MESSAGE
65
65
  1. Please replenishment TPC which is for paying tpc to UtxoProvider.
66
- UtxoProvider needs #{value_to_fill_utxo_pool} tapyrus in UTXO pool.
67
- UtxoProvider wallet's address is '#{address}'
66
+ UtxoProvider needs #{utxo_provider.value_to_fill_utxo_pool} tapyrus in UTXO pool.
67
+ UtxoProvider wallet's address is '#{utxo_provider.address}'
68
68
  2. Then create UTXOs for paying in UTXO pool with 'rake glueby:utxo_provider:manage_utxo_pool'
69
69
  MESSAGE
70
70
  else
@@ -72,12 +72,12 @@ module Glueby
72
72
  end
73
73
  end
74
74
 
75
- status = :not_ready if current_utxo_pool_size == 0
75
+ status = :not_ready if utxo_provider.current_utxo_pool_size == 0
76
76
 
77
77
  puts <<~EOS
78
78
  Status: #{STATUS[status]}
79
- TPC amount: #{delimit(tpc_amount)}
80
- UTXO pool size: #{delimit(current_utxo_pool_size)}
79
+ TPC amount: #{delimit(utxo_provider.tpc_amount)}
80
+ UTXO pool size: #{delimit(utxo_provider.current_utxo_pool_size)}
81
81
  #{"\n" if message}#{message}
82
82
  Configuration:
83
83
  default_value = #{delimit(utxo_provider.default_value)}
@@ -87,17 +87,13 @@ module Glueby
87
87
 
88
88
  # Show the address of Utxo Provider
89
89
  def print_address
90
- puts address
90
+ puts utxo_provider.address
91
91
  end
92
92
 
93
93
  private
94
94
 
95
- def tpc_amount
96
- wallet.balance(false)
97
- end
98
-
99
95
  def collect_outputs
100
- wallet.list_unspent.inject([0, []]) do |sum, output|
96
+ utxo_provider.wallet.list_unspent.inject([0, []]) do |sum, output|
101
97
  next sum if output[:color_id] || output[:amount] == utxo_provider.default_value
102
98
 
103
99
  new_sum = sum[0] + output[:amount]
@@ -105,34 +101,16 @@ module Glueby
105
101
  txid: output[:txid],
106
102
  script_pubkey: output[:script_pubkey],
107
103
  value: output[:amount],
108
- index: output[:vout] ,
104
+ index: output[:vout],
109
105
  finalized: output[:finalized]
110
106
  }
111
107
  [new_sum, new_outputs]
112
108
  end
113
109
  end
114
110
 
115
- def current_utxo_pool_size
116
- wallet
117
- .list_unspent(false)
118
- .count { |o| !o[:color_id] && o[:amount] == utxo_provider.default_value }
119
- end
120
-
121
- def value_to_fill_utxo_pool
122
- utxo_provider.default_value * utxo_provider.utxo_pool_size
123
- end
124
-
125
- def wallet
126
- utxo_provider.wallet
127
- end
128
-
129
111
  def delimit(num)
130
112
  num.to_s.reverse.scan(/.{1,3}/).join('_').reverse
131
113
  end
132
-
133
- def address
134
- @address ||= wallet.get_addresses.first || wallet.receive_address
135
- end
136
114
  end
137
115
  end
138
116
  end
@@ -27,7 +27,7 @@ module Glueby
27
27
  @fee_estimator = (UtxoProvider.config && UtxoProvider.config[:fee_estimator]) || Glueby::Contract::FixedFeeEstimator.new
28
28
  end
29
29
 
30
- attr_reader :wallet, :fee_estimator
30
+ attr_reader :wallet, :fee_estimator, :address
31
31
 
32
32
  # Provide a UTXO
33
33
  # @param [Tapyrus::Script] script_pubkey The script to be provided
@@ -78,6 +78,24 @@ module Glueby
78
78
  )
79
79
  end
80
80
 
81
+ def tpc_amount
82
+ wallet.balance(false)
83
+ end
84
+
85
+ def current_utxo_pool_size
86
+ wallet
87
+ .list_unspent(false)
88
+ .count { |o| !o[:color_id] && o[:amount] == default_value }
89
+ end
90
+
91
+ def address
92
+ @address ||= wallet.get_addresses.first || wallet.receive_address
93
+ end
94
+
95
+ def value_to_fill_utxo_pool
96
+ default_value * utxo_pool_size
97
+ end
98
+
81
99
  private
82
100
 
83
101
  # Create wallet for provider
@@ -1,3 +1,3 @@
1
1
  module Glueby
2
- VERSION = "0.8.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/glueby.rb CHANGED
@@ -48,4 +48,9 @@ module Glueby
48
48
  def self.configure
49
49
  yield configuration if block_given?
50
50
  end
51
+
52
+ # Base error classes. These error classes must be used as a super class in all error classes that is defined and
53
+ # raised in glueby library.
54
+ class Error < StandardError; end
55
+ class ArgumentError < Error; end
51
56
  end
@@ -4,7 +4,6 @@ module Glueby
4
4
  module Timestamp
5
5
  module_function
6
6
  extend Glueby::Contract::TxBuilder
7
- extend Glueby::Contract::Timestamp::Util
8
7
 
9
8
  def create
10
9
  timestamps = Glueby::Contract::AR::Timestamp.where(status: :init)
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.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-28 00:00:00.000000000 Z
11
+ date: 2022-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tapyrus
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.9
19
+ version: 0.2.13
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.9
26
+ version: 0.2.13
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -113,6 +113,10 @@ files:
113
113
  - lib/glueby/contract/payment.rb
114
114
  - lib/glueby/contract/timestamp.rb
115
115
  - lib/glueby/contract/timestamp/syncer.rb
116
+ - lib/glueby/contract/timestamp/tx_builder.rb
117
+ - lib/glueby/contract/timestamp/tx_builder/simple.rb
118
+ - lib/glueby/contract/timestamp/tx_builder/trackable.rb
119
+ - lib/glueby/contract/timestamp/tx_builder/updating_trackable.rb
116
120
  - lib/glueby/contract/token.rb
117
121
  - lib/glueby/contract/tx_builder.rb
118
122
  - lib/glueby/fee_provider.rb