glueby 1.2.3 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9490bd9983bf524a8b937c6b495ecb4260b65d8f84f590bffd4d06bcf8db3c60
4
- data.tar.gz: 187ab2c69864a94245dbb94c3c79abb1cfce1317138dcc9a9f6aea10b91864d6
3
+ metadata.gz: 41286c658b45bd9914f4c46f27e8e0f8696b61a8d763b5fcc2b0117d0e91668d
4
+ data.tar.gz: 3237815c98b14f3fc126e5531c8ff229d468fe77832bcca4b22828ffd386c7bd
5
5
  SHA512:
6
- metadata.gz: 71285a57ead31c478d4880ae64b1ab9e29091a22027f489fc92f30005d8cbf1eecf5a5e79f0bc81bbb8b99723399e4b6c1f856c8c2e9eb21ecadef64e591c593
7
- data.tar.gz: 7609a05ffd89aa3eb2dad0c75b9201a356bdcc83ff7f76522a8888b16f80071edb4d809093fb7e6e17322653aed701c1561d25c760073b75c21a00723996f632
6
+ metadata.gz: dad03d9f436a2720b854450fb95e8fab7fedde3d721f5db3bf0c69c1aa7b818ebfc359cda0c1057f4d98075295459f55f5261c69a4cc99a49f752bba3683e95c
7
+ data.tar.gz: eb8f0724b6b7b849a343b14e5630480584df5b6f408509dcdeebcca711055f8bcd3a62c188130b75118a7f93e18e015aeb2afd60784d4382cd27741b010f2c06
@@ -7,19 +7,15 @@
7
7
 
8
8
  name: Ruby
9
9
 
10
- on:
11
- push:
12
- branches: [master, v1.1, v1.2]
13
- pull_request:
14
- branches: [master, v1.1, v1.2]
10
+ on: [push,pull_request]
15
11
 
16
12
  jobs:
17
13
  test:
18
14
  runs-on: ubuntu-latest
19
15
  strategy:
20
16
  matrix:
21
- ruby-version: ["2.7", "3.0", "3.1", "3.2"]
22
-
17
+ ruby-version: ["3.0", "3.1", "3.2", "3.3"]
18
+ permissions: write-all
23
19
  steps:
24
20
  - run: docker pull tapyrus/tapyrusd:v0.5.2
25
21
  - uses: actions/checkout@v2
@@ -31,4 +27,4 @@ jobs:
31
27
  ruby-version: ${{ matrix.ruby-version }}
32
28
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
33
29
  - name: Run tests
34
- run: bundle exec rake
30
+ run: bundle exec rake
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.2.2
1
+ ruby-3.3.0
data/Gemfile CHANGED
@@ -6,3 +6,5 @@ gemspec
6
6
  gem "rake", "~> 12.0"
7
7
  gem "rspec", "~> 3.0"
8
8
  gem "docker-api", "~> 2.1.0"
9
+
10
+ gem "simplecov", require: false
data/glueby.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.description = %q{A Ruby library of smart contracts that can be used on Tapyrus.}
11
11
  spec.homepage = "https://github.com/chaintope/glueby"
12
12
  spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
14
14
 
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_runtime_dependency 'tapyrus', '>= 0.3.1'
30
30
  spec.add_runtime_dependency 'activerecord', '~> 7.0.0'
31
+ spec.add_runtime_dependency 'kaminari'
31
32
  spec.add_development_dependency 'sqlite3'
32
33
  spec.add_development_dependency 'mysql2'
33
34
  spec.add_development_dependency 'rails', '~> 7.0.0'
@@ -13,7 +13,7 @@ class CreateTimestamp < ActiveRecord::Migration<%= migration_version %>
13
13
  t.string :payment_base
14
14
  t.bigint :prev_id
15
15
  t.boolean :latest, null: false, default: true
16
- t.boolean :hex, null: false, default: false
16
+ t.string :version, null: false, default: "1"
17
17
  end
18
18
 
19
19
  add_index :glueby_timestamps, [:prev_id], unique: true
@@ -4,6 +4,7 @@ class CreateUtxo < ActiveRecord::Migration<%= migration_version %>
4
4
  t.string :txid
5
5
  t.integer :index
6
6
  t.bigint :value
7
+ t.string :color_id, index: true
7
8
  t.string :script_pubkey
8
9
  t.string :label, index: true
9
10
  t.integer :status
@@ -24,7 +24,7 @@ module Glueby
24
24
  end
25
25
 
26
26
  # Specify wallet adapter.
27
- # @param [Symbol] adapter - The adapter type :activerecord or :core is currently supported.
27
+ # @param [Symbol] adapter - The adapter type :activerecord, :core, or :mysql is currently supported.
28
28
  def wallet_adapter=(adapter)
29
29
  case adapter
30
30
  when :core
@@ -9,7 +9,14 @@ module Glueby
9
9
 
10
10
  attr_reader :tx
11
11
 
12
- belongs_to :prev, class_name: 'Glueby::Contract::AR::Timestamp', optional: true
12
+ belongs_to :prev, class_name: 'Glueby::Contract::AR::Timestamp', inverse_of: :next, optional: true
13
+ has_one :next, class_name: 'Glueby::Contract::AR::Timestamp', foreign_key: 'prev_id'
14
+
15
+ validates :version, presence: true, inclusion: { in: ['1', '2'] }
16
+
17
+ def next_id
18
+ self.next&.id
19
+ end
13
20
 
14
21
  validate :validate_prev
15
22
 
@@ -35,11 +42,14 @@ module Glueby
35
42
  # - content
36
43
  # - prefix(optional)
37
44
  # - timestamp_type(optional)
38
- # - hex(optional) [Boolean] If true, the strings set in prefix and content are treated as hex strings.
45
+ # - version [String] Version of the timestamp recording method.
46
+ # The format in which the timestamp is recorded differs depending on the version.
47
+ # Version "1" treats the specified content and prefix as a binary string.
48
+ # Version "2" treats the specified content and prefix as a hexadecimal string with the string set to prefix and content.
39
49
  # @raise [Glueby::ArgumentError] If the timestamp_type is not in :simple or :trackable
40
50
  def initialize(attributes = nil)
41
51
  # Set content_hash from :content attribute
42
- hex = attributes[:hex] || false
52
+ hex = attributes[:version] == '1' ? false : true
43
53
  content_hash = Timestamp.digest_content(attributes[:content], attributes[:digest] || :sha256, hex)
44
54
  super(
45
55
  wallet_id: attributes[:wallet_id],
@@ -48,7 +58,7 @@ module Glueby
48
58
  status: :init,
49
59
  timestamp_type: attributes[:timestamp_type] || :simple,
50
60
  prev_id: attributes[:prev_id],
51
- hex: hex
61
+ version: attributes[:version]
52
62
  )
53
63
  rescue ::ArgumentError => e
54
64
  raise Glueby::ArgumentError, e.message
@@ -114,6 +124,7 @@ module Glueby
114
124
 
115
125
  if update_trackable?
116
126
  prev.latest = false
127
+ prev.next = self
117
128
  prev.save!
118
129
  end
119
130
  end
@@ -129,6 +140,12 @@ module Glueby
129
140
  raise Errors::FailedToBroadcast, "failed to broadcast (id=#{id}, reason=#{e.message})"
130
141
  end
131
142
 
143
+ def hex?
144
+ version != '1'
145
+ end
146
+
147
+ alias_method :hex, :hex?
148
+
132
149
  private
133
150
 
134
151
  def wallet
@@ -139,11 +156,13 @@ module Glueby
139
156
  builder = builder_class.new(wallet, fee_estimator)
140
157
 
141
158
  if builder.instance_of?(Contract::Timestamp::TxBuilder::UpdatingTrackable)
159
+ prev_prefix = prev.hex ? prev.prefix&.htb : prev.prefix
160
+ prev_content_hash = prev.hex ? prev.content_hash&.htb : prev.content_hash
142
161
  builder.set_prev_timestamp_info(
143
162
  timestamp_utxo: prev.utxo,
144
163
  payment_base: prev.payment_base,
145
- prefix: prev.prefix,
146
- data: prev.content_hash
164
+ prefix: prev_prefix,
165
+ data: prev_content_hash
147
166
  )
148
167
  end
149
168
 
@@ -15,6 +15,7 @@ module Glueby
15
15
  class UnsupportedTokenType < ArgumentError; end
16
16
  class UnknownScriptPubkey < ArgumentError; end
17
17
  class UnsupportedDigestType < ArgumentError; end
18
+ class UnsupportedTimestampVersion < ArgumentError; end
18
19
  class PrevTimestampNotFound < ArgumentError; end
19
20
  class PrevTimestampIsNotTrackable < ArgumentError; end
20
21
  class UnnecessaryPrevTimestamp < ArgumentError; end
@@ -7,6 +7,51 @@ module Glueby
7
7
  # * 1 output to send the change TPC back to the wallet.
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
+ #
11
+ # == Versioning Timestamp
12
+ #
13
+ # The timestamp has an attribute called "version".
14
+ # Version indicates how the timestamp is recorded in the blockchain. Currently, only versions 1 and 2 are supported, and each is recorded in the following manner
15
+ # * Version 1: The first version of the blockchain is used to record timestamps.
16
+ # treats the content and prefix received as parameters as a binary string
17
+ # * Version 2:.
18
+ # treats the specified content and prefix as a hexadecimal string with the string set to prefix and content.
19
+ #
20
+ # For example, when recording a simple type of timestamp, the difference between the content recorded by version 1 and version 2 is as follows
21
+ # Version 1:
22
+ # Save the timestamp as follows:
23
+ #
24
+ # Glueby::Contract::Timestamp.new(
25
+ # wallet: wallet,
26
+ # content: "1234",
27
+ # prefix: "071222",
28
+ # digest: :none,
29
+ # version: "1"
30
+ # )
31
+ #
32
+ # The output script of the recorded transaction will include OP_RETURN and will look like this:
33
+ #
34
+ # OP_RETURN 30373132323231323334
35
+ #
36
+ # Note that prefix: "071222" and content: "1234" are interpreted as ASCII strings and their hexadecimal representation "3037313232323132323334" is recorded in the actual blockchain, respectively.
37
+ #
38
+ # Version 2:
39
+ #
40
+ # To save the timestamp in version 2, simply change the version of the previous example to "2".
41
+ #
42
+ # Glueby::Contract::Timestamp.new(
43
+ # wallet: wallet,
44
+ # content: "1234",
45
+ # prefix: "071222",
46
+ # digest: :none,
47
+ # version: "2"
48
+ # )
49
+ #
50
+ # The output script will look like this:
51
+ #
52
+ # OP_RETURN 0712221234
53
+ #
54
+ # In this case, prefix: "071222" and content: "1234" are treated as a hexadecimal string and recorded directly in the blockchain.
10
55
  class Timestamp
11
56
  P2C_DEFAULT_VALUE = 1_000
12
57
 
@@ -29,7 +74,10 @@ module Glueby
29
74
  # - :simple
30
75
  # - :trackable
31
76
  # @param [Integer] prev_timestamp_id The id column of glueby_timestamps that will be updated by the timestamp that will be created
32
- # @param [Boolean] hex If true, prefix and content are treated as hex strings
77
+ # @param [String] version Version of the timestamp recording method.
78
+ # The format in which the timestamp is recorded differs depending on the version.
79
+ # Version "1" treats the specified content and prefix as a binary string.
80
+ # Version "2" treats the specified content and prefix as a hexadecimal string with the string set to prefix and content.
33
81
  # @raise [Glueby::Contract::Errors::UnsupportedDigestType] if digest is unsupported
34
82
  # @raise [Glueby::Contract::Errors::InvalidTimestampType] if timestamp_type is unsupported
35
83
  def initialize(
@@ -41,7 +89,7 @@ module Glueby
41
89
  utxo_provider: nil,
42
90
  timestamp_type: :simple,
43
91
  prev_timestamp_id: nil,
44
- hex: false
92
+ version:
45
93
  )
46
94
  @wallet = wallet
47
95
  @content = content
@@ -53,7 +101,8 @@ module Glueby
53
101
  raise Glueby::Contract::Errors::InvalidTimestampType, "#{timestamp_type} is invalid type, supported types are :simple, and :trackable." unless [:simple, :trackable].include?(timestamp_type)
54
102
  @timestamp_type = timestamp_type
55
103
  @prev_timestamp_id = prev_timestamp_id
56
- @hex = hex
104
+ raise Glueby::Contract::Errors::UnsupportedTimestampVersion, "#{version} is unsupported, supported versions are '1' and '2'." unless ['1', '2'].include?(version)
105
+ @version = version
57
106
  end
58
107
 
59
108
  # broadcast to Tapyrus Core
@@ -70,7 +119,7 @@ module Glueby
70
119
  timestamp_type: @timestamp_type,
71
120
  digest: @digest,
72
121
  prev_id: @prev_timestamp_id,
73
- hex: @hex
122
+ version: @version
74
123
  )
75
124
  @ar.save_with_broadcast!(fee_estimator: @fee_estimator, utxo_provider: @utxo_provider)
76
125
  @ar.txid
@@ -86,27 +86,8 @@ module Glueby
86
86
  Glueby::AR::SystemInformation.use_only_finalized_utxo?
87
87
  end
88
88
 
89
- # Sign to pay-to-contract output.
90
- #
91
- # @param issuer [Glueby::Walelt] Issuer of the token
92
- # @param tx [Tapyrus::Tx] The transaction to be signed with metadata
93
- # @param funding_tx [Tapyrus::Tx] The funding transaction that has pay-to-contract output in its first output
94
- # @param payment_base [String] The public key used to generate pay to contract public key
95
- # @param metadata [String] Data that represents token metadata
96
- # @return [Tapyrus::Tx] signed tx
97
- def sign_to_p2c_output(issuer, tx, funding_tx, payment_base, metadata)
98
- utxo = { txid: funding_tx.txid, vout: 0, script_pubkey: funding_tx.outputs[0].script_pubkey.to_hex }
99
- issuer.internal_wallet.sign_to_pay_to_contract_address(tx, utxo, payment_base, metadata)
100
- end
101
-
102
89
  private
103
90
 
104
- def create_p2c_address(wallet, metadata)
105
- p2c_address, payment_base = wallet.internal_wallet.create_pay_to_contract_address(metadata)
106
- script = Tapyrus::Script.parse_from_addr(p2c_address)
107
- [script, p2c_address, payment_base]
108
- end
109
-
110
91
  def issue_reissuable_token(issuer:, amount:, split: 1, fee_estimator:, metadata: nil)
111
92
  txb = Internal::ContractBuilder.new(
112
93
  sender_wallet: issuer.internal_wallet,
@@ -244,14 +225,6 @@ module Glueby
244
225
  end
245
226
  end
246
227
  end
247
-
248
- # Add dummy inputs and outputs to tx for issue non-reissuable transaction and nft transaction
249
- def dummy_issue_tx_from_out_point
250
- tx = Tapyrus::Tx.new
251
- receiver_colored_script = Tapyrus::Script.parse_from_payload('21c20000000000000000000000000000000000000000000000000000000000000000bc76a914000000000000000000000000000000000000000088ac'.htb)
252
- tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: receiver_colored_script)
253
- FeeEstimator.dummy_tx(tx)
254
- end
255
228
  end
256
229
 
257
230
  attr_reader :color_id
@@ -206,8 +206,8 @@ module Glueby
206
206
  tx = Tapyrus::Tx.new
207
207
 
208
208
  amount = receivers.reduce(0) { |sum, r| sum + r[:amount].to_i }
209
- utxos = sender.internal_wallet.list_unspent(only_finalized)
210
- sum_token, outputs = collect_colored_outputs(utxos, color_id, amount)
209
+ utxos = sender.internal_wallet.list_unspent(only_finalized, color_id: color_id)
210
+ sum_token, outputs = collect_colored_outputs(utxos, amount)
211
211
  fill_input(tx, outputs)
212
212
 
213
213
  receivers.each do |r|
@@ -247,8 +247,8 @@ module Glueby
247
247
  def create_burn_tx(color_id:, sender:, amount: 0, fee_estimator: FeeEstimator::Fixed.new, only_finalized: true)
248
248
  tx = Tapyrus::Tx.new
249
249
 
250
- utxos = sender.internal_wallet.list_unspent(only_finalized)
251
- sum_token, outputs = collect_colored_outputs(utxos, color_id, amount)
250
+ utxos = sender.internal_wallet.list_unspent(only_finalized, color_id: color_id)
251
+ sum_token, outputs = collect_colored_outputs(utxos, amount)
252
252
  fill_input(tx, outputs)
253
253
 
254
254
  fill_change_token(tx, sender, sum_token - amount, color_id) if amount.positive?
@@ -319,12 +319,9 @@ module Glueby
319
319
  # Returns the set of utxos that satisfies the specified amount and has the specified color_id.
320
320
  # if amount is not specified or 0, return all utxos with color_id
321
321
  # @param results [Array] response of Glueby::Internal::Wallet#list_unspent
322
- # @param color_id [Tapyrus::Color::ColorIdentifier] color identifier
323
322
  # @param amount [Integer]
324
- def collect_colored_outputs(results, color_id, amount = 0)
323
+ def collect_colored_outputs(results, amount = 0)
325
324
  results = results.inject([0, []]) do |sum, output|
326
- next sum unless output[:color_id] == color_id.to_hex
327
-
328
325
  new_sum = sum[0] + output[:amount]
329
326
  new_outputs = sum[1] << output
330
327
  return [new_sum, new_outputs] if new_sum >= amount && amount.positive?
@@ -90,16 +90,6 @@ module Glueby
90
90
 
91
91
  private
92
92
 
93
- def check_wallet_amount!
94
- if tpc_amount < fee_provider.fixed_fee
95
- raise InsufficientTPC, <<~MESSAGE
96
- FeeProvider has insufficient TPC to create fee outputs to fill the UTXO pool.
97
- 1. Please replenishment TPC which is for paying fee to FeeProvider. FeeProvider needs #{fee_provider.utxo_pool_size * fee_provider.fixed_fee} tapyrus at least. FeeProvider wallet's address is '#{wallet.receive_address}'
98
- 2. Then create UTXOs for paying in UTXO pool with 'rake glueby:fee_provider:manage_utxo_pool'
99
- MESSAGE
100
- end
101
- end
102
-
103
93
  def tpc_amount
104
94
  wallet.balance(false)
105
95
  end
@@ -357,7 +357,7 @@ module Glueby
357
357
  end
358
358
 
359
359
  def get_fee_estimator(fee_estimator_name)
360
- Glueby::Contract::FeeEstimator.get_const("#{fee_estimator_name.capitalize}", false).new
360
+ Glueby::Contract::FeeEstimator.const_get(fee_estimator_name.capitalize, false).new
361
361
  end
362
362
 
363
363
  def valid_fee_estimator?(fee_estimator)
@@ -65,6 +65,21 @@ module Glueby
65
65
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
66
66
  end
67
67
 
68
+ # Returns all tokens with specified color_id
69
+ #
70
+ # @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method.
71
+ # @param [Boolean] only_finalized - includes only finalized UTXO value if it
72
+ # is true. Default is true.
73
+ # @param [Tapyrus::Color::ColorIdentifier] color_id The color identifier associated with UTXO.
74
+ # It will return only UTXOs with specified color_id. If color_id is nil, it will return all UTXOs.
75
+ # If Tapyrus::Color::ColorIdentifier.default is specified, it will return uncolored UTXOs(i.e. TPC)
76
+ # @param [Integer] page - The page parameter is responsible for specifying the current page being viewed within the paginated results. default is 1.
77
+ # @param [Integer] per - The per parameter is used to determine the number of items to display per page. default is 25.
78
+ # @return [Array<Utxo>] The array of the utxos with specified color_id
79
+ def list_unspent_with_count(wallet_id, only_finalized = true, label = nil, color_id: nil, page: 1, per: 25)
80
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
81
+ end
82
+
68
83
  # Returns the UTXOs that the wallet has.
69
84
  # If label is specified, return UTXOs filtered with label
70
85
  #
@@ -74,6 +89,9 @@ module Glueby
74
89
  # @param [String] label - Label for filtering UTXOs
75
90
  # - If label is nil or :unlabeled, only unlabeled UTXOs will be returned.
76
91
  # - If label=:all, it will return all utxos
92
+ # @param [Tapyrus::Color::ColorIdentifier] color_id - The color identifier.
93
+ # It will return only UTXOs with specified color_id. If color_id is nil, it will return all UTXOs.
94
+ # If Tapyrus::Color::ColorIdentifier.default is specified, it will return uncolored UTXOs(i.e. TPC)
77
95
  # @return [Array of UTXO]
78
96
  #
79
97
  # ## The UTXO structure
@@ -84,7 +102,7 @@ module Glueby
84
102
  # - finalized: [Boolean] Whether the UTXO is finalized
85
103
  # - color_id: [String] Color id of the UTXO. If it is TPC UTXO, color_id is nil.
86
104
  # - script_pubkey: [String] Script pubkey of the UTXO
87
- def list_unspent(wallet_id, only_finalized = true, label = nil)
105
+ def list_unspent(wallet_id, only_finalized = true, label = nil, color_id: nil)
88
106
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
89
107
  end
90
108
 
@@ -139,7 +157,7 @@ module Glueby
139
157
 
140
158
  # Returns information for the addresses
141
159
  #
142
- # @param [String] address - The p2pkh address to get information about
160
+ # @param [String, Array<String>] addresses - The p2pkh address to get information about
143
161
  # @return [Array<Hash>] The array of hash instance which has keys wallet_id, label and purpose.
144
162
  # Returns blank array if the key correspond with the address is not exist.
145
163
  def get_addresses_info(addresses)
@@ -38,6 +38,7 @@ module Glueby
38
38
  utxo = Utxo.find_or_initialize_by(txid: tx.txid, index: index)
39
39
  utxo.update!(
40
40
  label: key.label,
41
+ color_id: output.script_pubkey.color_id&.to_hex,
41
42
  script_pubkey: output.script_pubkey.to_hex,
42
43
  value: output.value,
43
44
  status: status,
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'securerandom'
4
+ require 'kaminari'
4
5
 
5
6
  module Glueby
6
7
  module Internal
@@ -89,29 +90,18 @@ module Glueby
89
90
  utxos.sum(&:value)
90
91
  end
91
92
 
92
- def list_unspent(wallet_id, only_finalized = true, label = nil)
93
- wallet = AR::Wallet.find_by(wallet_id: wallet_id)
94
- utxos = wallet.utxos.where(locked_at: nil)
95
- utxos = utxos.where(status: :finalized) if only_finalized
96
- if [:unlabeled, nil].include?(label)
97
- utxos = utxos.where(label: nil)
98
- elsif label && (label != :all)
99
- utxos = utxos.where(label: label)
100
- else
101
- utxos
102
- end
93
+ def list_unspent_with_count(wallet_id, only_finalized = true, label = nil, color_id: nil, page: 1, per: 25)
94
+ utxos = list_unspent_internal(wallet_id, color_id, only_finalized, label)
95
+ utxos = utxos.page(page).per(per) if per > 0
96
+ {
97
+ count: utxos.total_count,
98
+ outputs: utxos_to_h(utxos)
99
+ }
100
+ end
103
101
 
104
- utxos.map do |utxo|
105
- {
106
- txid: utxo.txid,
107
- vout: utxo.index,
108
- script_pubkey: utxo.script_pubkey,
109
- color_id: utxo.color_id,
110
- amount: utxo.value,
111
- finalized: utxo.status == 'finalized',
112
- label: utxo.label
113
- }
114
- end
102
+ def list_unspent(wallet_id, only_finalized = true, label = nil, color_id: nil)
103
+ utxos = list_unspent_internal(wallet_id, color_id, only_finalized, label)
104
+ utxos_to_h(utxos)
115
105
  end
116
106
 
117
107
  def sign_tx(wallet_id, tx, prevtxs = [], sighashtype: Tapyrus::SIGHASH_TYPE[:all])
@@ -245,6 +235,37 @@ module Glueby
245
235
  key_type: Tapyrus::Key::TYPES[:compressed]
246
236
  )
247
237
  end
238
+
239
+ def list_unspent_internal(wallet_id, color_id = nil, only_finalized = true, label = nil)
240
+ wallet = AR::Wallet.find_by(wallet_id: wallet_id)
241
+ utxos = wallet.utxos.where(locked_at: nil)
242
+ utxos = utxos.where(color_id: color_id.to_hex) if color_id && !color_id.default?
243
+ utxos = utxos.where(color_id: nil) if color_id && color_id.default?
244
+ utxos = utxos.where(status: :finalized) if only_finalized
245
+ utxos = utxos.order(:id)
246
+ utxos = if [:unlabeled, nil].include?(label)
247
+ utxos = utxos.where(label: nil)
248
+ elsif label && (label != :all)
249
+ utxos = utxos.where(label: label)
250
+ else
251
+ utxos
252
+ end
253
+ utxos
254
+ end
255
+
256
+ def utxos_to_h(utxos)
257
+ utxos.map do |utxo|
258
+ {
259
+ txid: utxo.txid,
260
+ vout: utxo.index,
261
+ script_pubkey: utxo.script_pubkey,
262
+ color_id: utxo.color_id,
263
+ amount: utxo.value,
264
+ finalized: utxo.status == 'finalized',
265
+ label: utxo.label
266
+ }
267
+ end
268
+ end
248
269
  end
249
270
  end
250
271
  end
@@ -84,7 +84,7 @@ module Glueby
84
84
 
85
85
  # If label=nil, it will return unlabeled utxos to protect labeled utxos for specific purpose
86
86
  # If label=:all, it will return all utxos
87
- def list_unspent(wallet_id, only_finalized = true, label = nil)
87
+ def list_unspent(wallet_id, only_finalized = true, label = nil, color_id: nil)
88
88
  perform_as(wallet_id) do |client|
89
89
  min_conf = only_finalized ? 1 : 0
90
90
  res = client.listunspent(min_conf)
@@ -97,6 +97,13 @@ module Glueby
97
97
  res
98
98
  end
99
99
 
100
+ if color_id
101
+ res = res.filter do |i|
102
+ script = Tapyrus::Script.parse_from_payload(i['scriptPubKey'].htb)
103
+ script.cp2pkh? || script.cp2sh? && color_id == Tapyrus::Color::ColorIdentifier.parse_from_payload(script.chunks[0].pushed_data)
104
+ end
105
+ end
106
+
100
107
  res.map do |i|
101
108
  script = Tapyrus::Script.parse_from_payload(i['scriptPubKey'].htb)
102
109
  color_id = if script.cp2pkh? || script.cp2sh?
@@ -177,7 +184,7 @@ module Glueby
177
184
  type = case sighashtype & (~(Tapyrus::SIGHASH_TYPE[:anyonecanpay]))
178
185
  when Tapyrus::SIGHASH_TYPE[:all] then 'ALL'
179
186
  when Tapyrus::SIGHASH_TYPE[:none] then 'NONE'
180
- when Tapyrus::SIGHASH_TYPE[:single] then 'SIGNLE'
187
+ when Tapyrus::SIGHASH_TYPE[:single] then 'SINGLE'
181
188
  else
182
189
  raise Errors::InvalidSighashType, "Invalid sighash type '#{sighashtype}'"
183
190
  end
@@ -93,9 +93,25 @@ module Glueby
93
93
  # @param label [String] This label is used to filtered the UTXOs with labeled if a key or Utxo is labeled.
94
94
  # - If label is nil or :unlabeled, only unlabeled UTXOs will be returned.
95
95
  # - If label=:all, all UTXOs will be returned.
96
- def list_unspent(only_finalized = true, label = :unlabeled)
96
+ # @param color_id [Tapyrus::Color::ColorIdentifier] The color identifier associated with UTXO.
97
+ # It will return only UTXOs with specified color_id. If color_id is nil, it will return all UTXOs.
98
+ # If Tapyrus::Color::ColorIdentifier.default is specified, it will return uncolored UTXOs(i.e. TPC)
99
+ # @param page [Integer] The page parameter is responsible for specifying the current page being viewed within the paginated results. default is 1.
100
+ # @param per [Integer] The per parameter is used to determine the number of items to display per page. default is 25.
101
+ def list_unspent_with_count(only_finalized = true, label = nil, color_id: nil, page: 1, per: 25)
102
+ wallet_adapter.list_unspent_with_count(id, only_finalized, label, color_id: color_id, page: page, per: per)
103
+ end
104
+
105
+ # @param only_finalized [Boolean] The flag to get a UTXO with status only finalized
106
+ # @param label [String] This label is used to filtered the UTXOs with labeled if a key or Utxo is labeled.
107
+ # - If label is nil or :unlabeled, only unlabeled UTXOs will be returned.
108
+ # - If label=:all, all UTXOs will be returned.
109
+ # @param color_id [Tapyrus::Color::ColorIdentifier] The color identifier associated with UTXO.
110
+ # It will return only UTXOs with specified color_id. If color_id is nil, it will return all UTXOs.
111
+ # If Tapyrus::Color::ColorIdentifier.default is specified, it will return uncolored UTXOs(i.e. TPC)
112
+ def list_unspent(only_finalized = true, label = :unlabeled, color_id: nil )
97
113
  label = :unlabeled unless label
98
- wallet_adapter.list_unspent(id, only_finalized, label)
114
+ wallet_adapter.list_unspent(id, only_finalized, label, color_id: color_id)
99
115
  end
100
116
 
101
117
  def lock_unspent(utxo)
@@ -171,8 +187,7 @@ module Glueby
171
187
  lock_utxos = false,
172
188
  excludes = []
173
189
  )
174
- collect_utxos(amount, label, only_finalized, shuffle, lock_utxos, excludes) do |output|
175
- next false unless output[:color_id].nil?
190
+ collect_utxos(amount, label, Tapyrus::Color::ColorIdentifier.default, only_finalized, shuffle, lock_utxos, excludes) do |output|
176
191
  next yield(output) if block_given?
177
192
 
178
193
  true
@@ -206,8 +221,7 @@ module Glueby
206
221
  lock_utxos = false,
207
222
  excludes = []
208
223
  )
209
- collect_utxos(amount, label, only_finalized, shuffle, lock_utxos, excludes) do |output|
210
- next false unless output[:color_id] == color_id.to_hex
224
+ collect_utxos(amount, label, color_id, only_finalized, shuffle, lock_utxos, excludes) do |output|
211
225
  next yield(output) if block_given?
212
226
 
213
227
  true
@@ -261,7 +275,7 @@ module Glueby
261
275
  while current_amount - fee < target_amount
262
276
  sum, utxos = collect_uncolored_outputs(
263
277
  fee + target_amount - current_amount,
264
- nil, nil, true, true,
278
+ nil, false, true, true,
265
279
  provided_utxos,
266
280
  &block
267
281
  )
@@ -288,7 +302,8 @@ module Glueby
288
302
  def collect_utxos(
289
303
  amount,
290
304
  label,
291
- only_finalized,
305
+ color_id,
306
+ only_finalized = true,
292
307
  shuffle = true,
293
308
  lock_utxos = false,
294
309
  excludes = []
@@ -296,7 +311,7 @@ module Glueby
296
311
  collect_all = amount.nil?
297
312
 
298
313
  raise Glueby::ArgumentError, 'amount must be positive' unless collect_all || amount.positive?
299
- utxos = list_unspent(only_finalized, label)
314
+ utxos = list_unspent(only_finalized, label, color_id: color_id)
300
315
  utxos = utxos.shuffle if shuffle
301
316
 
302
317
  r = utxos.inject([0, []]) do |(sum, outputs), output|
@@ -1,3 +1,3 @@
1
1
  module Glueby
2
- VERSION = "1.2.3"
2
+ VERSION = "1.3.0"
3
3
  end
data/lib/glueby/wallet.rb CHANGED
@@ -46,6 +46,10 @@ module Glueby
46
46
  end
47
47
  end
48
48
 
49
+ def token_utxos(color_id = nil, only_finalized = true, page = 1, per = 25)
50
+ @internal_wallet.list_unspent_with_count(only_finalized, nil, color_id: color_id, page: page, per: per)
51
+ end
52
+
49
53
  private
50
54
 
51
55
  def initialize(internal_wallet)
data/lib/glueby.rb CHANGED
@@ -19,7 +19,7 @@ module Glueby
19
19
 
20
20
  module GluebyLogger
21
21
  def logger
22
- if defined?(Rails)
22
+ if defined?(Rails) && Rails.logger
23
23
  Rails.logger
24
24
  else
25
25
  Logger.new(STDOUT)
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: 1.2.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-18 00:00:00.000000000 Z
11
+ date: 2024-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tapyrus
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 7.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: kaminari
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: sqlite3
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -173,7 +187,7 @@ metadata:
173
187
  homepage_uri: https://github.com/chaintope/glueby
174
188
  source_code_uri: https://github.com/chaintope/glueby
175
189
  changelog_uri: https://github.com/chaintope/glueby
176
- post_install_message:
190
+ post_install_message:
177
191
  rdoc_options: []
178
192
  require_paths:
179
193
  - lib
@@ -181,15 +195,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
181
195
  requirements:
182
196
  - - ">="
183
197
  - !ruby/object:Gem::Version
184
- version: 2.7.0
198
+ version: 3.0.0
185
199
  required_rubygems_version: !ruby/object:Gem::Requirement
186
200
  requirements:
187
201
  - - ">="
188
202
  - !ruby/object:Gem::Version
189
203
  version: '0'
190
204
  requirements: []
191
- rubygems_version: 3.4.10
192
- signing_key:
205
+ rubygems_version: 3.5.3
206
+ signing_key:
193
207
  specification_version: 4
194
208
  summary: A Ruby library of smart contracts that can be used on Tapyrus.
195
209
  test_files: []