steem-ruby 0.9.1 → 0.9.6

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.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/README.md +83 -10
  4. data/Rakefile +128 -31
  5. data/lib/steem.rb +49 -0
  6. data/lib/steem/api.rb +38 -36
  7. data/lib/steem/base_error.rb +32 -11
  8. data/lib/steem/block_api.rb +23 -3
  9. data/lib/steem/broadcast.rb +165 -2
  10. data/lib/steem/marshal.rb +231 -0
  11. data/lib/steem/mixins/jsonable.rb +37 -0
  12. data/lib/steem/mixins/retriable.rb +22 -13
  13. data/lib/steem/mixins/serializable.rb +45 -0
  14. data/lib/steem/operation.rb +141 -0
  15. data/lib/steem/operation/account_create.rb +10 -0
  16. data/lib/steem/operation/account_create_with_delegation.rb +12 -0
  17. data/lib/steem/operation/account_update.rb +8 -0
  18. data/lib/steem/operation/account_witness_proxy.rb +4 -0
  19. data/lib/steem/operation/account_witness_vote.rb +5 -0
  20. data/lib/steem/operation/cancel_transfer_from_savings.rb +4 -0
  21. data/lib/steem/operation/challenge_authority.rb +5 -0
  22. data/lib/steem/operation/change_recovery_account.rb +5 -0
  23. data/lib/steem/operation/claim_account.rb +5 -0
  24. data/lib/steem/operation/claim_reward_balance.rb +6 -0
  25. data/lib/steem/operation/comment.rb +9 -0
  26. data/lib/steem/operation/comment_options.rb +10 -0
  27. data/lib/steem/operation/convert.rb +5 -0
  28. data/lib/steem/operation/create_claimed_account.rb +10 -0
  29. data/lib/steem/operation/custom.rb +5 -0
  30. data/lib/steem/operation/custom_binary.rb +8 -0
  31. data/lib/steem/operation/custom_json.rb +6 -0
  32. data/lib/steem/operation/decline_voting_rights.rb +4 -0
  33. data/lib/steem/operation/delegate_vesting_shares.rb +5 -0
  34. data/lib/steem/operation/delete_comment.rb +4 -0
  35. data/lib/steem/operation/escrow_approve.rb +8 -0
  36. data/lib/steem/operation/escrow_dispute.rb +7 -0
  37. data/lib/steem/operation/escrow_release.rb +10 -0
  38. data/lib/steem/operation/escrow_transfer.rb +12 -0
  39. data/lib/steem/operation/feed_publish.rb +4 -0
  40. data/lib/steem/operation/limit_order_cancel.rb +4 -0
  41. data/lib/steem/operation/limit_order_create.rb +8 -0
  42. data/lib/steem/operation/limit_order_create2.rb +8 -0
  43. data/lib/steem/operation/prove_authority.rb +4 -0
  44. data/lib/steem/operation/recover_account.rb +6 -0
  45. data/lib/steem/operation/report_over_production.rb +5 -0
  46. data/lib/steem/operation/request_account_recovery.rb +6 -0
  47. data/lib/steem/operation/reset_account.rb +5 -0
  48. data/lib/steem/operation/set_reset_account.rb +5 -0
  49. data/lib/steem/operation/set_withdraw_vesting_route.rb +6 -0
  50. data/lib/steem/operation/transfer.rb +6 -0
  51. data/lib/steem/operation/transfer_from_savings.rb +7 -0
  52. data/lib/steem/operation/transfer_to_savings.rb +6 -0
  53. data/lib/steem/operation/transfer_to_vesting.rb +5 -0
  54. data/lib/steem/operation/vote.rb +6 -0
  55. data/lib/steem/operation/withdraw_vesting.rb +4 -0
  56. data/lib/steem/operation/witness_set_properties.rb +5 -0
  57. data/lib/steem/operation/witness_update.rb +7 -0
  58. data/lib/steem/rpc/base_client.rb +14 -2
  59. data/lib/steem/rpc/http_client.rb +17 -1
  60. data/lib/steem/stream.rb +385 -0
  61. data/lib/steem/transaction.rb +96 -0
  62. data/lib/steem/transaction_builder.rb +77 -70
  63. data/lib/steem/type/amount.rb +6 -0
  64. data/lib/steem/version.rb +1 -1
  65. data/steem-ruby.gemspec +9 -4
  66. metadata +203 -56
  67. data/Gemfile.lock +0 -73
@@ -1,16 +1,16 @@
1
1
  module Steem
2
2
  class BaseError < StandardError
3
- def initialize(error, cause = nil)
3
+ def initialize(error = nil, cause = nil)
4
4
  @error = error
5
5
  @cause = cause
6
6
  end
7
7
 
8
8
  def to_s
9
- if !!@cause
10
- JSON[error: @error, cause: @cause] rescue {error: @error, cause: @cause}.to_s
11
- else
12
- JSON[@error] rescue @error.to_s
13
- end
9
+ detail = {}
10
+ detail[:error] = @error if !!@error
11
+ detail[:cause] = @cause if !!@cause
12
+
13
+ JSON[detail] rescue detail.to_s
14
14
  end
15
15
 
16
16
  def self.build_error(error, context)
@@ -19,7 +19,11 @@ module Steem
19
19
  end
20
20
 
21
21
  if error.message.include? 'Internal Error'
22
- raise Steem::RemoteNodeError.new, error.message, build_backtrace(error)
22
+ raise Steem::RemoteNodeError, error.message, build_backtrace(error)
23
+ end
24
+
25
+ if error.message.include? 'Server error'
26
+ raise Steem::RemoteNodeError, error.message, build_backtrace(error)
23
27
  end
24
28
 
25
29
  if error.message.include? 'plugin not enabled'
@@ -30,6 +34,10 @@ module Steem
30
34
  raise Steem::ArgumentError, "#{context}: #{error.message}", build_backtrace(error)
31
35
  end
32
36
 
37
+ if error.message.include? 'Invalid params'
38
+ raise Steem::ArgumentError, "#{context}: #{error.message}", build_backtrace(error)
39
+ end
40
+
33
41
  if error.message.start_with? 'Bad Cast:'
34
42
  raise Steem::ArgumentError, "#{context}: #{error.message}", build_backtrace(error)
35
43
  end
@@ -110,6 +118,10 @@ module Steem
110
118
  raise Steem::MissingOtherAuthorityError, "#{context}: #{error.message}", build_backtrace(error)
111
119
  end
112
120
 
121
+ if error.message.include? 'Upstream response error'
122
+ raise Steem::UpstreamResponseError, "#{context}: #{error.message}", build_backtrace(error)
123
+ end
124
+
113
125
  if error.message.include? 'Bad or missing upstream response'
114
126
  raise Steem::BadOrMissingUpstreamResponseError, "#{context}: #{error.message}", build_backtrace(error)
115
127
  end
@@ -122,6 +134,10 @@ module Steem
122
134
  raise Steem::InvalidAccountError, "#{context}: #{error.message}", build_backtrace(error)
123
135
  end
124
136
 
137
+ if error.message.include?('Method') && error.message.include?(' does not exist.')
138
+ raise Steem::UnknownMethodError, "#{context}: #{error.message}", build_backtrace(error)
139
+ end
140
+
125
141
  if error.message.include? 'Invalid operation name'
126
142
  raise Steem::UnknownOperationError, "#{context}: #{error.message}", build_backtrace(error)
127
143
  end
@@ -160,11 +176,10 @@ module Steem
160
176
  end
161
177
  end
162
178
 
179
+ class DeserializationError < BaseError; end
180
+ class SerializationMismatchError < BaseError; end
163
181
  class UnsupportedChainError < BaseError; end
164
182
  class ArgumentError < BaseError; end
165
- class RemoteNodeError < BaseError; end
166
- class RemoteDatabaseLockError < RemoteNodeError; end
167
- class PluginNotEnabledError < RemoteNodeError; end
168
183
  class TypeError < BaseError; end
169
184
  class EmptyTransactionError < ArgumentError; end
170
185
  class InvalidAccountError < ArgumentError; end
@@ -186,12 +201,18 @@ module Steem
186
201
  class MissingOtherAuthorityError < MissingAuthorityError; end
187
202
  class IncorrectRequestIdError < BaseError; end
188
203
  class IncorrectResponseIdError < BaseError; end
189
- class BadOrMissingUpstreamResponseError < BaseError; end
204
+ class RemoteNodeError < BaseError; end
205
+ class UpstreamResponseError < RemoteNodeError; end
206
+ class RemoteDatabaseLockError < UpstreamResponseError; end
207
+ class PluginNotEnabledError < UpstreamResponseError; end
208
+ class BadOrMissingUpstreamResponseError < UpstreamResponseError; end
190
209
  class TransactionIndexDisabledError < BaseError; end
191
210
  class NotAppBaseError < BaseError; end
192
211
  class UnknownApiError < BaseError; end
212
+ class UnknownMethodError < BaseError; end
193
213
  class UnknownOperationError < BaseError; end
194
214
  class JsonRpcBatchMaximumSizeExceededError < BaseError; end
195
215
  class TooManyTimeoutsError < BaseError; end
216
+ class TooManyRetriesError < BaseError; end
196
217
  class UnknownError < BaseError; end
197
218
  end
@@ -12,11 +12,25 @@ module Steem
12
12
  super
13
13
  end
14
14
 
15
+ # Uses a batched requst on a range of block headers.
16
+ #
17
+ # @param options [Hash] The attributes to get a block range with.
18
+ # @option options [Range] :block_range starting on one block number and ending on an higher block number.
19
+ def get_block_headers(options = {block_range: (0..0)}, &block)
20
+ get_block_objects(options.merge(object: :block_header), block)
21
+ end
22
+
15
23
  # Uses a batched requst on a range of blocks.
16
24
  #
17
25
  # @param options [Hash] The attributes to get a block range with.
18
26
  # @option options [Range] :block_range starting on one block number and ending on an higher block number.
19
27
  def get_blocks(options = {block_range: (0..0)}, &block)
28
+ get_block_objects(options.merge(object: :block), block)
29
+ end
30
+ private
31
+ def get_block_objects(options = {block_range: (0..0)}, block = nil)
32
+ object = options[:object]
33
+ object_method = "get_#{object}".to_sym
20
34
  block_range = options[:block_range] || (0..0)
21
35
 
22
36
  if (start = block_range.first) < 1
@@ -33,15 +47,21 @@ module Steem
33
47
  request_object = []
34
48
 
35
49
  for i in sub_range do
36
- @rpc_client.put(self.class.api_name, :get_block, block_num: i, request_object: request_object)
50
+ @rpc_client.put(self.class.api_name, object_method, block_num: i, request_object: request_object)
37
51
  end
38
52
 
39
53
  if !!block
40
54
  index = 0
41
55
  @rpc_client.rpc_batch_execute(request_object: request_object) do |result, error, id|
42
56
  block_num = sub_range.to_a[index]
43
- index = index += 1
44
- yield(result.nil? ? nil : result.block, block_num)
57
+ index = index + 1
58
+
59
+ case object
60
+ when :block_header
61
+ block.call(result.nil? ? nil : result[:header], block_num)
62
+ else
63
+ block.call(result.nil? ? nil : result[object], block_num)
64
+ end
45
65
  end
46
66
  else
47
67
  blocks = []
@@ -31,6 +31,7 @@ module Steem
31
31
  # For details on what to pass to these methods, check out the {https://developers.steem.io/apidefinitions/broadcast-ops Steem Developer Portal Broadcast Operations} page.
32
32
  class Broadcast
33
33
  extend Retriable
34
+ extend Utils
34
35
 
35
36
  DEFAULT_MAX_ACCEPTED_PAYOUT = Type::Amount.new(amount: '1000000000', precision: 3, nai: '@@000000013')
36
37
 
@@ -196,13 +197,17 @@ module Steem
196
197
  permlink: params[:permlink],
197
198
  max_accepted_payout: max_accepted_payout,
198
199
  percent_steem_dollars: params[:percent_steem_dollars] || 10000,
200
+ # allow_replies: allow_replies,
199
201
  allow_votes: allow_votes,
200
202
  allow_curation_rewards: allow_curation_rewards,
201
203
  extensions: []
202
204
  }
203
205
 
204
206
  if !!params[:beneficiaries]
205
- comment_options[:extensions] << [0, {beneficiaries: params[:beneficiaries]}]
207
+ comment_options[:extensions] << [
208
+ comment_options[:extensions].size,
209
+ normalize_beneficiaries(options.merge(beneficiaries: params[:beneficiaries]))
210
+ ]
206
211
  end
207
212
 
208
213
  ops << [:comment_options, comment_options]
@@ -509,6 +514,68 @@ module Steem
509
514
  process(options.merge(ops: ops), &block)
510
515
  end
511
516
 
517
+ # Create a claimed account.
518
+ # options = {
519
+ # wif: wif,
520
+ # params: {
521
+ # creator: creator_account_name,
522
+ # new_account_name: new_account_name,
523
+ # owner: {
524
+ # weight_threshold: 1,
525
+ # account_auths: [],
526
+ # key_auths: [[owner_public_key, 1]],
527
+ # },
528
+ # active: {
529
+ # weight_threshold: 1,
530
+ # account_auths: [],
531
+ # key_auths: [[active_public_key, 1]],
532
+ # },
533
+ # posting: {
534
+ # weight_threshold: 1,
535
+ # account_auths: [],
536
+ # key_auths: [[posting_public_key, 1]],
537
+ # },
538
+ # memo_key: memo_public_key,
539
+ # json_metadata: '{}'
540
+ # }
541
+ # }
542
+ #
543
+ # Steem::Broadcast.create_claimed_account(options)
544
+ #
545
+ # @param options [Hash] options
546
+ # @option options [String] :wif Active wif
547
+ # @option options [Hash] :params
548
+ # * :creator (String)
549
+ # * :new_account_name (String)
550
+ # * :owner (Hash)
551
+ # * :active (Hash)
552
+ # * :posting (Hash)
553
+ # * :memo_key (String)
554
+ # * :metadata (Hash) Metadata of the account, becomes `json_metadata`.
555
+ # * :json_metadata (String) String version of `metadata` (use one or the other).
556
+ # * :extensions (Array) (optional)
557
+ # @option options [Boolean] :pretend Just validate, do not broadcast.
558
+ # @see https://developers.steem.io/apidefinitions/broadcast-ops#broadcast_ops_create_claimed_account
559
+ def self.create_claimed_account(options, &block)
560
+ required_fields = %i(creator new_account_name owner active posting memo_key json_metadata)
561
+ params = options[:params]
562
+
563
+ if !!params[:metadata] && !!params[:json_metadata]
564
+ raise Steem::ArgumentError, 'Assign either metadata or json_metadata, not both.'
565
+ end
566
+
567
+ metadata = params.delete(:metadata) || {}
568
+ metadata ||= (JSON[params[:json_metadata]] || nil) || {}
569
+ params[:json_metadata] = metadata.to_json
570
+
571
+ check_required_fields(params, *required_fields)
572
+
573
+ params[:extensions] ||= []
574
+ ops = [[:create_claimed_account, params]]
575
+
576
+ process(options.merge(ops: ops), &block)
577
+ end
578
+
512
579
  # Update an account.
513
580
  # options = {
514
581
  # wif: wif,
@@ -611,6 +678,73 @@ module Steem
611
678
  process(options.merge(ops: ops), &block)
612
679
  end
613
680
 
681
+ # Extensible replacement for #witness_update that supports additional
682
+ # properties added since HF20 and beyond.
683
+ #
684
+ # options = {
685
+ # wif: wif,
686
+ # params: {
687
+ # owner: witness_account_name,
688
+ # props: {
689
+ # account_creation_fee: '0.000 STEEM',
690
+ # maximum_block_size: 131072,
691
+ # sbd_interest_rate: 1000,
692
+ # account_subsidy_budget: 50000,
693
+ # account_subsidy_decay: 330782,
694
+ # sbd_exchange_rate: '1.000 STEEM',
695
+ # url: "https://steemit.com",
696
+ # new_signing_key: 'STM8LoQjQqJHvotqBo7HjnqmUbFW9oJ2theyqonzUd9DdJ7YYHsvD'
697
+ # }
698
+ # }
699
+ # }
700
+ #
701
+ # Steem::Broadcast.witness_set_properties(options)
702
+ #
703
+ # @param options [Hash] options
704
+ # @option options [String] :wif Active wif
705
+ # @option options [Hash] :params
706
+ # * :owner (String)
707
+ # * :props (String)
708
+ # @option options [Boolean] :pretend Just validate, do not broadcast.
709
+ # @see https://developers.steem.io/apidefinitions/broadcast-ops#broadcast_ops_witness_set_properties
710
+ # @see https://github.com/steemit/steem/blob/master/doc/witness_parameters.md
711
+ def self.witness_set_properties(options, &block)
712
+ required_fields = %i(owner props)
713
+ params = options[:params]
714
+ check_required_fields(params, *required_fields)
715
+
716
+ if !!(account_creation_fee = params[:props][:account_creation_fee] rescue nil)
717
+ params[:props][:account_creation_fee] = normalize_amount(options.merge amount: account_creation_fee, serialize: true)
718
+ end
719
+
720
+ if !!(sbd_exchange_rate = params[:props][:sbd_exchange_rate] rescue nil)
721
+ params[:props][:sbd_exchange_rate][:base] = normalize_amount(options.merge amount: sbd_exchange_rate[:base], serialize: true)
722
+ params[:props][:sbd_exchange_rate][:quote] = normalize_amount(options.merge amount: sbd_exchange_rate[:quote], serialize: true)
723
+ params[:props][:sbd_exchange_rate] = params[:props][:sbd_exchange_rate].to_json
724
+ end
725
+
726
+ %i(key new_signing_key).each do |key|
727
+ if !!params[key] && params[key].size == 53
728
+ params[key] = params[key][3..-1]
729
+ end
730
+ end
731
+
732
+ %i(account_creation_fee sbd_exchange_rate url new_signing_key).each do |key|
733
+ next unless !!params[:props][key]
734
+
735
+ val = params[:props][key].to_s
736
+
737
+ params[:props][key] = hexlify val unless val =~ /^[0-9A-F]+$/i
738
+ end
739
+
740
+ params[:props] = params[:props].to_a
741
+
742
+ params[:extensions] ||= []
743
+ ops = [[:witness_set_properties, params]]
744
+
745
+ process(options.merge(ops: ops), &block)
746
+ end
747
+
614
748
  # All accounts with a VFS (Vesting Fund Shares) can vote for or against any
615
749
  # witness.
616
750
  #
@@ -1084,6 +1218,28 @@ module Steem
1084
1218
  process(options.merge(ops: ops), &block)
1085
1219
  end
1086
1220
 
1221
+ # @param options [Hash] options
1222
+ # @option options [String] :wif Active wif
1223
+ # @option options [Hash] :params
1224
+ # * :creator (String)
1225
+ # * :fee (String)
1226
+ # * :extensions (Array)
1227
+ # @option options [Boolean] :pretend Just validate, do not broadcast.
1228
+ # @see https://developers.steem.io/apidefinitions/broadcast-ops#broadcast_ops_claim_account
1229
+ def self.claim_account(options, &block)
1230
+ required_fields = %i(creator fee)
1231
+ params = options[:params]
1232
+
1233
+ check_required_fields(params, *required_fields)
1234
+
1235
+ params[:fee] = normalize_amount(options.merge amount: params[:fee])
1236
+ params[:extensions] ||= []
1237
+
1238
+ ops = [[:claim_account, params]]
1239
+
1240
+ process(options.merge(ops: ops), &block)
1241
+ end
1242
+
1087
1243
  # @param options [Hash] options
1088
1244
  # @option options [Array<Array<Hash>] :ops Operations to process.
1089
1245
  # @option options [Boolean] :pretend Just validate, do not broadcast.
@@ -1131,12 +1287,19 @@ module Steem
1131
1287
  def self.normalize_amount(options)
1132
1288
  if !!options[:app_base]
1133
1289
  Type::Amount.to_h(options[:amount])
1290
+ elsif !!options[:serialize]
1291
+ Type::Amount.to_s(options[:amount])
1134
1292
  else
1135
1293
  Type::Amount.to_s(options[:amount])
1136
1294
  end
1137
1295
  end
1138
1296
 
1139
- # @privats
1297
+ def self.normalize_beneficiaries(options)
1298
+ # Type::Beneficiaries.new(options[:beneficiaries])
1299
+ {beneficiaries: options[:beneficiaries]}
1300
+ end
1301
+
1302
+ # @private
1140
1303
  def self.database_api(options)
1141
1304
  options[:database_api] ||= if !!options[:app_base]
1142
1305
  Steem::DatabaseApi.new(options)
@@ -0,0 +1,231 @@
1
+ require 'bindata'
2
+ require 'base58'
3
+
4
+ module Steem
5
+ class Marshal
6
+ include Utils
7
+ include ChainConfig
8
+
9
+ PUBLIC_KEY_DISABLED = '1111111111111111111111111111111114T1Anm'
10
+
11
+ attr_reader :bytes, :cursor
12
+
13
+ def initialize(options = {})
14
+ @bytes = if !!(hex = options[:hex])
15
+ unhexlify hex
16
+ else
17
+ options[:bytes]
18
+ end
19
+
20
+ @chain = options[:chain] || :steem
21
+ @prefix ||= case @chain
22
+ when :steem then NETWORKS_STEEM_ADDRESS_PREFIX
23
+ when :test then NETWORKS_TEST_ADDRESS_PREFIX
24
+ else; raise UnsupportedChainError, "Unsupported chain: #{@chain}"
25
+ end
26
+ @cursor = 0
27
+ end
28
+
29
+ def hex
30
+ hexlify bytes
31
+ end
32
+
33
+ def rewind!
34
+ @cursor = 0
35
+ end
36
+
37
+ def step(n = 0)
38
+ @cursor += n
39
+ end
40
+
41
+ def scan(len)
42
+ bytes.slice(@cursor..(@cursor - 1) + len).tap { |_| @cursor += len }
43
+ end
44
+
45
+ def operation_type
46
+ Operation::IDS[unsigned_char]
47
+ end
48
+
49
+ def unsigned_char; BinData::Uint8le.read(scan(1)); end # 8-bit unsigned
50
+ def uint16; BinData::Uint16le.read(scan(2)); end # 16-bit unsigned, VAX (little-endian) byte order
51
+ def uint32; BinData::Uint32le.read(scan(4)); end # 32-bit unsigned, VAX (little-endian) byte order
52
+ def uint64; BinData::Uint64le.read(scan(8)); end # 64-bit unsigned, little-endian
53
+
54
+ def signed_char; BinData::Int8le.read(scan(1)); end # 8-bit signed
55
+ def int16; BinData::Int16le.read(scan(2)); end # 16-bit signed, little-endian
56
+ def int32; BinData::Int32le.read(scan(4)); end # 32-bit signed, little-endian
57
+ def int64; BinData::Int64le.read(scan(8)); end # 64-bit signed, little-endian
58
+
59
+ def boolean; scan(1) == "\x01"; end
60
+
61
+ def varint
62
+ shift = 0
63
+ result = 0
64
+ bytes = []
65
+
66
+ while (n = unsigned_char) >> 7 == 1
67
+ bytes << n
68
+ end
69
+
70
+ bytes << n
71
+
72
+ bytes.each do |b|
73
+ result += ((b & 0x7f) << shift)
74
+ break unless (b & 0x80)
75
+ shift += 7
76
+ end
77
+
78
+ result
79
+ end
80
+
81
+ def string(len = nil); scan(len || varint); end
82
+
83
+ def raw_bytes(len = nil); scan(len || varint).force_encoding('BINARY'); end
84
+
85
+ def point_in_time
86
+ if (time = uint32) == 2**32-1
87
+ Time.at -1
88
+ else
89
+ Time.at time
90
+ end.utc
91
+ end
92
+
93
+ def public_key(prefix = @prefix)
94
+ raw_public_key = raw_bytes(33)
95
+ checksum = OpenSSL::Digest::RIPEMD160.digest(raw_public_key)
96
+ key = Base58.binary_to_base58(raw_public_key + checksum.slice(0, 4), :bitcoin)
97
+
98
+ prefix + key unless key == PUBLIC_KEY_DISABLED
99
+ end
100
+
101
+ def amount
102
+ amount = uint64.to_f
103
+ precision = signed_char
104
+ asset = scan(7).strip
105
+
106
+ amount = "%.#{precision}f #{asset}" % (amount / 10 ** precision)
107
+
108
+ Steem::Type::Amount.new(amount)
109
+ end
110
+
111
+ def price
112
+ {base: amount, quote: amount}
113
+ end
114
+
115
+ def authority(options = {optional: false})
116
+ return if !!options[:optional] && unsigned_char == 0
117
+
118
+ {
119
+ weight_threshold: uint32,
120
+ account_auths: varint.times.map { [string, uint16] },
121
+ key_auths: varint.times.map { [public_key, uint16] }
122
+ }
123
+ end
124
+
125
+ def optional_authority
126
+ authority(optional: true)
127
+ end
128
+
129
+ def comment_options_extensions
130
+ if scan(1) == "\x01"
131
+ beneficiaries
132
+ else
133
+ []
134
+ end
135
+ end
136
+
137
+ def beneficiaries
138
+ if scan(1) == "\x00"
139
+ varint.times.map {{account: string, weight: uint16}}
140
+ end
141
+ end
142
+
143
+ def chain_properties
144
+ {
145
+ account_creation_fee: amount,
146
+ maximum_block_size: uint32,
147
+ sbd_interest_rate: uint16
148
+ }
149
+ end
150
+
151
+ def required_auths
152
+ varint.times.map { string }
153
+ end
154
+
155
+ def witness_properties
156
+ properties = {}
157
+
158
+ varint.times do
159
+ key = string.to_sym
160
+ properties[key] = case key
161
+ when :account_creation_fee then Steem::Type::Amount.new(string)
162
+ when :account_subsidy_budget then scan(3)
163
+ when :account_subsidy_decay, :maximum_block_size then uint32
164
+ when :url then string
165
+ when :sbd_exchange_rate
166
+ JSON[string].tap do |rate|
167
+ rate["base"] = Steem::Type::Amount.new(rate["base"])
168
+ rate["quote"] = Steem::Type::Amount.new(rate["quote"])
169
+ end
170
+ when :sbd_interest_rate then uint16
171
+ when :key, :new_signing_key then @prefix + scan(50)
172
+ else; raise "Unknown witness property: #{key}"
173
+ end
174
+ end
175
+
176
+ properties
177
+ end
178
+
179
+ def empty_array
180
+ unsigned_char == 0 and [] or raise "Found non-empty array."
181
+ end
182
+
183
+ def transaction(options = {})
184
+ trx = options[:trx] || Transaction.new
185
+
186
+ trx.ref_block_num = uint16
187
+ trx.ref_block_prefix = uint32
188
+ trx.expiration = point_in_time
189
+
190
+ trx.operations = operations
191
+
192
+ trx
193
+ rescue => e
194
+ raise DeserializationError.new("Transaction failed\nOriginal serialized bytes:\n[#{hex[0..(@cursor * 2) - 1]}]#{hex[((@cursor) * 2)..-1]}", e)
195
+ end
196
+
197
+ def operations
198
+ operations_len = signed_char
199
+ operations = []
200
+
201
+ while operations.size < operations_len do
202
+ begin
203
+ type = operation_type
204
+ break if type.nil?
205
+
206
+ op_class_name = type.to_s.sub!(/_operation$/, '')
207
+ op_class_name = "Steem::Operation::" + op_class_name.split('_').map(&:capitalize).join
208
+ op_class = Object::const_get(op_class_name)
209
+ op = op_class.new
210
+
211
+ op_class::serializable_types.each do |k, v|
212
+ begin
213
+ # binding.pry if v == :comment_options_extensions
214
+ op.send("#{k}=", send(v))
215
+ rescue => e
216
+ raise DeserializationError.new("#{type}.#{k} (#{v}) failed", e)
217
+ end
218
+ end
219
+
220
+ operations << {type: type, value: op}
221
+ rescue => e
222
+ raise DeserializationError.new("#{type} failed", e)
223
+ end
224
+ end
225
+
226
+ operations
227
+ rescue => e
228
+ raise DeserializationError.new("Operations failed", e)
229
+ end
230
+ end
231
+ end