glueby 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/glueby.gemspec CHANGED
@@ -1,33 +1,33 @@
1
- require_relative 'lib/glueby/version'
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = "glueby"
5
- spec.version = Glueby::VERSION
6
- spec.authors = ["azuchi"]
7
- spec.email = ["azuchi@chaintope.com"]
8
-
9
- spec.summary = %q{A Ruby library of smart contracts that can be used on Tapyrus.}
10
- spec.description = %q{A Ruby library of smart contracts that can be used on Tapyrus.}
11
- spec.homepage = "https://github.com/chaintope/glueby"
12
- spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
-
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/chaintope/glueby"
18
- spec.metadata["changelog_uri"] = "https://github.com/chaintope/glueby"
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
- end
25
- spec.bindir = "exe"
26
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
- spec.require_paths = ["lib"]
28
-
29
- spec.add_runtime_dependency 'tapyrus', '>= 0.2.9'
30
- spec.add_runtime_dependency 'activerecord', '~> 6.1.3'
31
- spec.add_development_dependency 'sqlite3'
32
- spec.add_development_dependency 'rails', '~> 6.1.3'
33
- end
1
+ require_relative 'lib/glueby/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "glueby"
5
+ spec.version = Glueby::VERSION
6
+ spec.authors = ["azuchi"]
7
+ spec.email = ["azuchi@chaintope.com"]
8
+
9
+ spec.summary = %q{A Ruby library of smart contracts that can be used on Tapyrus.}
10
+ spec.description = %q{A Ruby library of smart contracts that can be used on Tapyrus.}
11
+ spec.homepage = "https://github.com/chaintope/glueby"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/chaintope/glueby"
18
+ spec.metadata["changelog_uri"] = "https://github.com/chaintope/glueby"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_runtime_dependency 'tapyrus', '>= 0.2.9'
30
+ spec.add_runtime_dependency 'activerecord', '~> 6.1.3'
31
+ spec.add_development_dependency 'sqlite3'
32
+ spec.add_development_dependency 'rails', '~> 6.1.3'
33
+ end
@@ -1,8 +1,8 @@
1
- # Edit configuration for connection to tapyrus core
2
- Glueby.configure do |config|
3
- config.wallet_adapter = :activerecord
4
- config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
5
- end
6
-
7
- # Uncomment next line when using timestamp feature
8
- # Glueby::BlockSyncer.register_syncer(Glueby::Contract::Timestamp::Syncer)
1
+ # Edit configuration for connection to tapyrus core
2
+ Glueby.configure do |config|
3
+ config.wallet_adapter = :activerecord
4
+ config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
5
+ end
6
+
7
+ # Uncomment next line when using timestamp feature
8
+ # Glueby::BlockSyncer.register_syncer(Glueby::Contract::Timestamp::Syncer)
@@ -1,16 +1,16 @@
1
- class CreateKey < ActiveRecord::Migration<%= migration_version %>
2
- def change
3
- create_table :glueby_keys<%= table_options %> do |t|
4
- t.string :private_key
5
- t.string :public_key
6
- t.string :script_pubkey
7
- t.string :label, index: true
8
- t.integer :purpose
9
- t.belongs_to :wallet
10
- t.timestamps
11
- end
12
-
13
- add_index :glueby_keys, [:script_pubkey], unique: true
14
- add_index :glueby_keys, [:private_key], unique: true
15
- end
16
- end
1
+ class CreateKey < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :glueby_keys<%= table_options %> do |t|
4
+ t.string :private_key
5
+ t.string :public_key
6
+ t.string :script_pubkey
7
+ t.string :label, index: true
8
+ t.integer :purpose
9
+ t.belongs_to :wallet
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :glueby_keys, [:script_pubkey], unique: true
14
+ add_index :glueby_keys, [:private_key], unique: true
15
+ end
16
+ end
@@ -1,16 +1,16 @@
1
- class CreateUtxo < ActiveRecord::Migration<%= migration_version %>
2
- def change
3
- create_table :glueby_utxos<%= table_options %> do |t|
4
- t.string :txid
5
- t.integer :index
6
- t.bigint :value
7
- t.string :script_pubkey
8
- t.string :label, index: true
9
- t.integer :status
10
- t.belongs_to :key, null: true
11
- t.timestamps
12
- end
13
-
14
- add_index :glueby_utxos, [:txid, :index], unique: true
15
- end
16
- end
1
+ class CreateUtxo < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :glueby_utxos<%= table_options %> do |t|
4
+ t.string :txid
5
+ t.integer :index
6
+ t.bigint :value
7
+ t.string :script_pubkey
8
+ t.string :label, index: true
9
+ t.integer :status
10
+ t.belongs_to :key, null: true
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :glueby_utxos, [:txid, :index], unique: true
15
+ end
16
+ end
@@ -1,98 +1,98 @@
1
- module Glueby
2
- # You can use BlockSyncer when you need to synchronize the state of
3
- # an application with the state of a blockchain. When BlockSyncer
4
- # detects the generation of a new block, it executes the registered
5
- # syncer code on a block-by-block or transaction-by-transaction basis.
6
- # By using this, an application can detect that the issued transaction
7
- # has been captured in blocks, receive a new remittance, and so on.
8
- #
9
- # # Syncer logic registration
10
- #
11
- # For registration, create a class that implements the method that performs
12
- # synchronization processing and registers it in BlockSyncer. Implement
13
- # methods with the following name in that class.
14
- #
15
- # Method name | Arguments | Call conditions
16
- # ------------------ | --------------------- | ------------------------------
17
- # block_sync (block) | block: Tapyrus::Block | When a new block is created
18
- # block_tx (tx) | tx: Tapyrus::Tx | When a new block is created, it is executed for each tx contained in that block.
19
- #
20
- # @example Register a synchronous logic
21
- # class Syncer
22
- # def block_sync (block)
23
- # # sync a block
24
- # end
25
- #
26
- # def tx_sync (tx)
27
- # # sync a tx
28
- # end
29
- # end
30
- # BlockSyncer.register_syncer(Syncer)
31
- #
32
- # @example Unregister the synchronous logic
33
- # BlockSyncer.unregister_syncer(Syncer)
34
- #
35
- # # Run BlockSyncer
36
- #
37
- # Run the `glueby: block_syncer: start` rake task periodically with a program
38
- # for periodic execution such as cron. If it detects the generation of a new
39
- # block when it is executed, the synchronization process will be executed.
40
- # Determine the execution interval according to the requirements of the application.
41
- class BlockSyncer
42
- # @!attribute [r] height
43
- # @return [Integer] The block height to be synced
44
- attr_reader :height
45
-
46
- class << self
47
- # @!attribute r syncers
48
- # @return [Array<Class>] The syncer classes that is registered
49
- attr_reader :syncers
50
-
51
- # Register syncer class
52
- # @param [Class] syncer The syncer to be registered.
53
- def register_syncer(syncer)
54
- @syncers ||= []
55
- @syncers << syncer
56
- end
57
-
58
- # Unregister syncer class
59
- # @param [Class] syncer The syncer to be unregistered.
60
- def unregister_syncer(syncer)
61
- @syncers ||= []
62
- @syncers.delete(syncer)
63
- end
64
- end
65
-
66
- # @param [Integer] height The block height to be synced in the instance
67
- def initialize(height)
68
- @height = height
69
- end
70
-
71
- # Run a block synchronization
72
- def run
73
- return if self.class.syncers.nil?
74
-
75
- self.class.syncers.each do |syncer|
76
- instance = syncer.new
77
- instance.block_sync(block) if instance.respond_to?(:block_sync)
78
-
79
- if instance.respond_to?(:tx_sync)
80
- block.transactions.each { |tx| instance.tx_sync(tx) }
81
- end
82
- end
83
- end
84
-
85
- private
86
-
87
- def block
88
- @block ||= begin
89
- block = Glueby::Internal::RPC.client.getblock(block_hash, 0)
90
- Tapyrus::Block.parse_from_payload(block.htb)
91
- end
92
- end
93
-
94
- def block_hash
95
- @block_hash ||= Glueby::Internal::RPC.client.getblockhash(height)
96
- end
97
- end
1
+ module Glueby
2
+ # You can use BlockSyncer when you need to synchronize the state of
3
+ # an application with the state of a blockchain. When BlockSyncer
4
+ # detects the generation of a new block, it executes the registered
5
+ # syncer code on a block-by-block or transaction-by-transaction basis.
6
+ # By using this, an application can detect that the issued transaction
7
+ # has been captured in blocks, receive a new remittance, and so on.
8
+ #
9
+ # # Syncer logic registration
10
+ #
11
+ # For registration, create a class that implements the method that performs
12
+ # synchronization processing and registers it in BlockSyncer. Implement
13
+ # methods with the following name in that class.
14
+ #
15
+ # Method name | Arguments | Call conditions
16
+ # ------------------ | --------------------- | ------------------------------
17
+ # block_sync (block) | block: Tapyrus::Block | When a new block is created
18
+ # block_tx (tx) | tx: Tapyrus::Tx | When a new block is created, it is executed for each tx contained in that block.
19
+ #
20
+ # @example Register a synchronous logic
21
+ # class Syncer
22
+ # def block_sync (block)
23
+ # # sync a block
24
+ # end
25
+ #
26
+ # def tx_sync (tx)
27
+ # # sync a tx
28
+ # end
29
+ # end
30
+ # BlockSyncer.register_syncer(Syncer)
31
+ #
32
+ # @example Unregister the synchronous logic
33
+ # BlockSyncer.unregister_syncer(Syncer)
34
+ #
35
+ # # Run BlockSyncer
36
+ #
37
+ # Run the `glueby: block_syncer: start` rake task periodically with a program
38
+ # for periodic execution such as cron. If it detects the generation of a new
39
+ # block when it is executed, the synchronization process will be executed.
40
+ # Determine the execution interval according to the requirements of the application.
41
+ class BlockSyncer
42
+ # @!attribute [r] height
43
+ # @return [Integer] The block height to be synced
44
+ attr_reader :height
45
+
46
+ class << self
47
+ # @!attribute r syncers
48
+ # @return [Array<Class>] The syncer classes that is registered
49
+ attr_reader :syncers
50
+
51
+ # Register syncer class
52
+ # @param [Class] syncer The syncer to be registered.
53
+ def register_syncer(syncer)
54
+ @syncers ||= []
55
+ @syncers << syncer
56
+ end
57
+
58
+ # Unregister syncer class
59
+ # @param [Class] syncer The syncer to be unregistered.
60
+ def unregister_syncer(syncer)
61
+ @syncers ||= []
62
+ @syncers.delete(syncer)
63
+ end
64
+ end
65
+
66
+ # @param [Integer] height The block height to be synced in the instance
67
+ def initialize(height)
68
+ @height = height
69
+ end
70
+
71
+ # Run a block synchronization
72
+ def run
73
+ return if self.class.syncers.nil?
74
+
75
+ self.class.syncers.each do |syncer|
76
+ instance = syncer.new
77
+ instance.block_sync(block) if instance.respond_to?(:block_sync)
78
+
79
+ if instance.respond_to?(:tx_sync)
80
+ block.transactions.each { |tx| instance.tx_sync(tx) }
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def block
88
+ @block ||= begin
89
+ block = Glueby::Internal::RPC.client.getblock(block_hash, 0)
90
+ Tapyrus::Block.parse_from_payload(block.htb)
91
+ end
92
+ end
93
+
94
+ def block_hash
95
+ @block_hash ||= Glueby::Internal::RPC.client.getblockhash(height)
96
+ end
97
+ end
98
98
  end
@@ -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,27 @@ 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
61
85
  end
62
86
  end
@@ -1,13 +1,13 @@
1
- module Glueby
2
- module Contract
3
- class Timestamp
4
- class Syncer
5
- def block_sync(block)
6
- Glueby::Contract::AR::Timestamp
7
- .where(txid: block.transactions.map(&:txid), status: :unconfirmed)
8
- .update_all(status: :confirmed)
9
- end
10
- end
11
- end
12
- end
13
- end
1
+ module Glueby
2
+ module Contract
3
+ class Timestamp
4
+ class Syncer
5
+ def block_sync(block)
6
+ Glueby::Contract::AR::Timestamp
7
+ .where(txid: block.transactions.map(&:txid), status: :unconfirmed)
8
+ .update_all(status: :confirmed)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,102 +1,108 @@
1
- module Glueby
2
- module Contract
3
- # Timestamp feature allows users to send transaction with op_return output which has sha256 hash of arbitary data.
4
- # Timestamp transaction has
5
- # * 1 or more inputs enough to afford transaction fee.
6
- # * 1 output which has op_return, application specific prefix, and sha256 hash of data.
7
- # * 1 output to send the change TPC back to the wallet.
8
- #
9
- # Storing timestamp transaction to the blockchain enables everyone to verify that the data existed at that time and a user signed it.
10
- class Timestamp
11
- include Glueby::Contract::TxBuilder
12
-
13
- autoload :Syncer, 'glueby/contract/timestamp/syncer'
14
-
15
- module Util
16
- include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
17
- module_function
18
-
19
- def create_tx(wallet, prefix, data, fee_estimator)
20
- tx = Tapyrus::Tx.new
21
- tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: create_script(prefix, data))
22
-
23
- fee = fee_estimator.fee(dummy_tx(tx))
24
- sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
25
- fill_input(tx, outputs)
26
-
27
- fill_change_tpc(tx, wallet, sum - fee)
28
- wallet.internal_wallet.sign_tx(tx)
29
- end
30
-
31
- def create_payload(prefix, data)
32
- payload = +''
33
- payload << prefix
34
- payload << data
35
- payload
36
- end
37
-
38
- def create_script(prefix, data)
39
- script = Tapyrus::Script.new
40
- script << Tapyrus::Script::OP_RETURN
41
- script << create_payload(prefix, data)
42
- script
43
- end
44
-
45
- def get_transaction(tx)
46
- Glueby::Internal::RPC.client.getrawtransaction(tx.txid, 1)
47
- end
48
- end
49
- include Glueby::Contract::Timestamp::Util
50
-
51
- attr_reader :tx, :txid
52
-
53
- # @param [String] content Data to be hashed and stored in blockchain.
54
- # @param [String] prefix prefix of op_return data
55
- # @param [Glueby::Contract::FeeEstimator] fee_estimator
56
- # @param [Symbol] digest type which select of:
57
- # - :sha256
58
- # - :double_sha256
59
- # - :none
60
- # @raise [Glueby::Contract::Errors::UnsupportedDigestType] if digest unsupport
61
- def initialize(
62
- wallet:,
63
- content:,
64
- prefix: '',
65
- fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
66
- digest: :sha256
67
- )
68
- @wallet = wallet
69
- @content = content
70
- @prefix = prefix
71
- @fee_estimator = fee_estimator
72
- @digest = digest
73
- end
74
-
75
- # broadcast to Tapyrus Core
76
- # @return [String] txid
77
- # @raise [TxAlreadyBroadcasted] if tx has been broadcasted.
78
- # @raise [InsufficientFunds] if result of listunspent is not enough to pay the specified amount
79
- def save!
80
- raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
81
-
82
- @tx = create_tx(@wallet, @prefix, digest_content, @fee_estimator)
83
- @txid = @wallet.internal_wallet.broadcast(@tx)
84
- end
85
-
86
- private
87
-
88
- def digest_content
89
- case @digest&.downcase
90
- when :sha256
91
- Tapyrus.sha256(@content)
92
- when :double_sha256
93
- Tapyrus.double_sha256(@content)
94
- when :none
95
- @content
96
- else
97
- raise Glueby::Contract::Errors::UnsupportedDigestType
98
- end
99
- end
100
- end
101
- end
102
- end
1
+ module Glueby
2
+ module Contract
3
+ # Timestamp feature allows users to send transaction with op_return output which has sha256 hash of arbitary data.
4
+ # Timestamp transaction has
5
+ # * 1 or more inputs enough to afford transaction fee.
6
+ # * 1 output which has op_return, application specific prefix, and sha256 hash of data.
7
+ # * 1 output to send the change TPC back to the wallet.
8
+ #
9
+ # Storing timestamp transaction to the blockchain enables everyone to verify that the data existed at that time and a user signed it.
10
+ class Timestamp
11
+ include Glueby::Contract::TxBuilder
12
+
13
+ autoload :Syncer, 'glueby/contract/timestamp/syncer'
14
+
15
+ module Util
16
+ include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
17
+ module_function
18
+
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
+ txb.fee(fee).change_address(wallet.internal_wallet.change_address)
45
+ [funding_tx, wallet.internal_wallet.sign_tx(txb.build)]
46
+ end
47
+
48
+ def get_transaction(tx)
49
+ Glueby::Internal::RPC.client.getrawtransaction(tx.txid, 1)
50
+ end
51
+ end
52
+ include Glueby::Contract::Timestamp::Util
53
+
54
+ attr_reader :tx, :txid
55
+
56
+ # @param [String] content Data to be hashed and stored in blockchain.
57
+ # @param [String] prefix prefix of op_return data
58
+ # @param [Glueby::Contract::FeeEstimator] fee_estimator
59
+ # @param [Symbol] digest type which select of:
60
+ # - :sha256
61
+ # - :double_sha256
62
+ # - :none
63
+ # @raise [Glueby::Contract::Errors::UnsupportedDigestType] if digest unsupport
64
+ def initialize(
65
+ wallet:,
66
+ content:,
67
+ prefix: '',
68
+ fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
69
+ digest: :sha256,
70
+ utxo_provider: nil
71
+ )
72
+ @wallet = wallet
73
+ @content = content
74
+ @prefix = prefix
75
+ @fee_estimator = fee_estimator
76
+ @digest = digest
77
+ @utxo_provider = utxo_provider
78
+ end
79
+
80
+ # broadcast to Tapyrus Core
81
+ # @return [String] txid
82
+ # @raise [TxAlreadyBroadcasted] if tx has been broadcasted.
83
+ # @raise [InsufficientFunds] if result of listunspent is not enough to pay the specified amount
84
+ def save!
85
+ raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
86
+
87
+ funding_tx, @tx = create_txs(@wallet, @prefix, digest_content, @fee_estimator, @utxo_provider)
88
+ @wallet.internal_wallet.broadcast(funding_tx) if funding_tx
89
+ @txid = @wallet.internal_wallet.broadcast(@tx)
90
+ end
91
+
92
+ private
93
+
94
+ def digest_content
95
+ case @digest&.downcase
96
+ when :sha256
97
+ Tapyrus.sha256(@content)
98
+ when :double_sha256
99
+ Tapyrus.double_sha256(@content)
100
+ when :none
101
+ @content
102
+ else
103
+ raise Glueby::Contract::Errors::UnsupportedDigestType
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end