steem-ruby 0.9.1 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
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