stellar-base 0.24.0 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -56
  3. data/README.md +7 -7
  4. data/generated/stellar-base-generated.rb +15 -0
  5. data/generated/stellar/account_flags.rb +9 -4
  6. data/generated/stellar/account_merge_result.rb +1 -1
  7. data/generated/stellar/allow_trust_op.rb +3 -18
  8. data/generated/stellar/asset_code.rb +30 -0
  9. data/generated/stellar/begin_sponsoring_future_reserves_result.rb +2 -1
  10. data/generated/stellar/claimable_balance_entry.rb +2 -0
  11. data/generated/stellar/claimable_balance_entry/ext.rb +4 -0
  12. data/generated/stellar/claimable_balance_entry_extension_v1.rb +30 -0
  13. data/generated/stellar/claimable_balance_entry_extension_v1/ext.rb +24 -0
  14. data/generated/stellar/claimable_balance_flags.rb +22 -0
  15. data/generated/stellar/clawback_claimable_balance_op.rb +18 -0
  16. data/generated/stellar/clawback_claimable_balance_result.rb +26 -0
  17. data/generated/stellar/clawback_claimable_balance_result_code.rb +29 -0
  18. data/generated/stellar/clawback_op.rb +22 -0
  19. data/generated/stellar/clawback_result.rb +25 -0
  20. data/generated/stellar/clawback_result_code.rb +31 -0
  21. data/generated/stellar/create_passive_sell_offer_op.rb +1 -1
  22. data/generated/stellar/end_sponsoring_future_reserves_result.rb +2 -1
  23. data/generated/stellar/operation.rb +6 -0
  24. data/generated/stellar/operation/body.rb +12 -0
  25. data/generated/stellar/operation_id.rb +1 -1
  26. data/generated/stellar/operation_id/id.rb +2 -2
  27. data/generated/stellar/operation_result.rb +6 -0
  28. data/generated/stellar/operation_result/tr.rb +12 -0
  29. data/generated/stellar/operation_type.rb +7 -1
  30. data/generated/stellar/payment_result_code.rb +1 -1
  31. data/generated/stellar/revoke_sponsorship_op.rb +1 -2
  32. data/generated/stellar/set_options_result_code.rb +14 -11
  33. data/generated/stellar/set_trust_line_flags_op.rb +25 -0
  34. data/generated/stellar/set_trust_line_flags_result.rb +25 -0
  35. data/generated/stellar/set_trust_line_flags_result_code.rb +31 -0
  36. data/generated/stellar/transaction_result_code.rb +1 -1
  37. data/generated/stellar/trust_line_flags.rb +5 -1
  38. data/lib/stellar-base.rb +6 -2
  39. data/lib/stellar/account.rb +59 -0
  40. data/lib/stellar/asset.rb +10 -0
  41. data/lib/stellar/compat.rb +6 -7
  42. data/lib/stellar/concerns/transaction.rb +5 -4
  43. data/lib/stellar/dsl.rb +32 -5
  44. data/lib/stellar/ext/xdr.rb +8 -7
  45. data/lib/stellar/key_pair.rb +22 -23
  46. data/lib/stellar/ledger_key.rb +4 -2
  47. data/lib/stellar/muxed_account.rb +16 -0
  48. data/lib/stellar/operation.rb +89 -19
  49. data/lib/stellar/transaction.rb +1 -1
  50. data/lib/stellar/transaction_builder.rb +20 -7
  51. data/lib/stellar/transaction_envelope.rb +6 -16
  52. data/lib/stellar/transaction_v0.rb +2 -10
  53. data/lib/stellar/trust_line_flags.rb +53 -0
  54. data/lib/stellar/util/strkey.rb +15 -7
  55. data/lib/stellar/version.rb +3 -0
  56. metadata +34 -16
  57. data/generated/stellar/allow_trust_op/asset.rb +0 -33
  58. data/lib/stellar/base/version.rb +0 -5
@@ -39,11 +39,12 @@ XDR::DSL::Union.redefine_method(:switch) do |switch, arm = nil|
39
39
  end
40
40
  end
41
41
 
42
- # XDR::Union generates an attribute method for each `arm`, but lacks the
43
- # actual `attribute(attr)` method those generated methods delegate to.
44
- # We follow the semantics of the bang variant `XDR::Union#attribute!` method,
45
- # except that calls to `raise` are replaced with early returns of nil.
46
- XDR::Union.define_method(:attribute) do |attr|
47
- return unless @arm.to_s == attr
48
- get
42
+ # XDR::Union delegates missing methods to the underlying value
43
+ XDR::Union.define_method(:method_missing) do |name, *args|
44
+ return super(name, *args) unless value&.respond_to?(name)
45
+ value&.public_send(name, *args)
46
+ end
47
+
48
+ XDR::Union.define_method(:respond_to_missing?) do |*args|
49
+ value&.respond_to?(*args)
49
50
  end
@@ -6,6 +6,11 @@ module Stellar
6
6
  from_raw_seed seed_bytes
7
7
  end
8
8
 
9
+ def from_address(address)
10
+ pk_bytes = Util::StrKey.check_decode(:account_id, address)
11
+ from_public_key(pk_bytes)
12
+ end
13
+
9
14
  def from_raw_seed(seed_bytes)
10
15
  secret_key = RbNaCl::SigningKey.new(seed_bytes)
11
16
  public_key = secret_key.verify_key
@@ -17,11 +22,6 @@ module Stellar
17
22
  new(public_key)
18
23
  end
19
24
 
20
- def from_address(address)
21
- pk_bytes = Util::StrKey.check_decode(:account_id, address)
22
- from_public_key(pk_bytes)
23
- end
24
-
25
25
  def random
26
26
  secret_key = RbNaCl::SigningKey.generate
27
27
  public_key = secret_key.verify_key
@@ -40,11 +40,21 @@ module Stellar
40
40
 
41
41
  extend FactoryMethods
42
42
 
43
+ # @param [RbNaCl::VerifyKey] public_key
44
+ # @param [RbNaCl::SigningKey, nil] secret_key
43
45
  def initialize(public_key, secret_key = nil)
44
46
  @public_key = public_key
45
47
  @secret_key = secret_key
46
48
  end
47
49
 
50
+ def address
51
+ Util::StrKey.check_encode(:account_id, raw_public_key)
52
+ end
53
+
54
+ def seed
55
+ Util::StrKey.check_encode(:seed, raw_seed)
56
+ end
57
+
48
58
  def account_id
49
59
  Stellar::AccountID.new :public_key_type_ed25519, raw_public_key
50
60
  end
@@ -61,16 +71,17 @@ module Stellar
61
71
  Stellar::SignerKey.new :signer_key_type_ed25519, raw_public_key
62
72
  end
63
73
 
64
- def raw_public_key
65
- @public_key.to_bytes
66
- end
67
-
68
74
  def signature_hint
69
75
  # take last 4 bytes
70
76
  account_id.to_xdr.slice(-4, 4)
71
77
  end
72
78
 
79
+ def raw_public_key
80
+ @public_key.to_bytes
81
+ end
82
+
73
83
  def raw_seed
84
+ raise "no private key" if @secret_key.nil?
74
85
  @secret_key.to_bytes
75
86
  end
76
87
 
@@ -82,25 +93,13 @@ module Stellar
82
93
  @public_key
83
94
  end
84
95
 
85
- def address
86
- pk_bytes = raw_public_key
87
- Util::StrKey.check_encode(:account_id, pk_bytes)
88
- end
89
-
90
- def seed
91
- raise "no private key" if @secret_key.nil?
92
- # TODO: improve the error class above
93
- seed_bytes = raw_seed
94
- Util::StrKey.check_encode(:seed, seed_bytes)
95
- end
96
-
97
96
  def sign?
98
97
  !@secret_key.nil?
99
98
  end
100
99
 
101
100
  def sign(message)
102
- raise "no private key" if @secret_key.nil?
103
- # TODO: improve the error class above
101
+ raise NotImplementedError, "no private key, signing is not available" unless sign?
102
+
104
103
  @secret_key.sign(message)
105
104
  end
106
105
 
@@ -4,6 +4,8 @@ require "stellar/dsl"
4
4
  module Stellar
5
5
  class LedgerKey
6
6
  class << self
7
+ include Stellar::DSL
8
+
7
9
  def switch_for_arm(name)
8
10
  (@switch_by_arm ||= switches.invert).fetch(name)
9
11
  end
@@ -12,7 +14,7 @@ module Stellar
12
14
  field, value = options.first
13
15
  case field
14
16
  when nil
15
- account(account_id: Stellar.KeyPair(account_id).account_id)
17
+ account(account_id: KeyPair(account_id).account_id)
16
18
  when :balance_id
17
19
  claimable_balance(balance_id: ClaimableBalanceID.v0(Stellar::Convert.from_hex(value.to_s)))
18
20
  when :offer_id
@@ -20,7 +22,7 @@ module Stellar
20
22
  when :data_name
21
23
  data(account_id: account_id, data_name: value.to_s)
22
24
  when :asset
23
- trust_line(account_id: account_id, asset: Stellar.Asset(value))
25
+ trust_line(account_id: account_id, asset: Asset(value))
24
26
  else
25
27
  raise ArgumentError, "unknown option #{field} (not in :asset, :offer_id, :data_name, :balance_id)"
26
28
  end
@@ -0,0 +1,16 @@
1
+ module Stellar
2
+ class MuxedAccount
3
+ def to_keypair
4
+ case arm
5
+ when :ed25519 then KeyPair.from_public_key(value)
6
+ when :med25519 then KeyPair.from_public_key(value.ed25519)
7
+ else
8
+ raise "impossible"
9
+ end
10
+ end
11
+
12
+ def address
13
+ to_keypair.address
14
+ end
15
+ end
16
+ end
@@ -3,8 +3,14 @@ require "bigdecimal"
3
3
  module Stellar
4
4
  class Operation
5
5
  MAX_INT64 = 2**63 - 1
6
+ TRUST_LINE_FLAGS_MAPPING = {
7
+ full: Stellar::TrustLineFlags.authorized_flag,
8
+ maintain_liabilities: Stellar::TrustLineFlags.authorized_to_maintain_liabilities_flag,
9
+ clawback_enabled: Stellar::TrustLineFlags.trustline_clawback_enabled_flag
10
+ }.freeze
6
11
 
7
12
  class << self
13
+ include Stellar::DSL
8
14
  #
9
15
  # Construct a new Stellar::Operation from the provided
10
16
  # source account and body
@@ -16,16 +22,17 @@ module Stellar
16
22
  # @return [Stellar::Operation] the built operation
17
23
  def make(attributes = {})
18
24
  source_account = attributes[:source_account]
19
- body = Stellar::Operation::Body.new(*attributes[:body])
20
-
21
- op = Stellar::Operation.new(body: body)
22
25
 
23
- if source_account
24
- raise ArgumentError, "Bad :source_account" unless source_account.is_a?(Stellar::KeyPair)
25
- op.source_account = source_account.muxed_account
26
+ if source_account && !source_account.is_a?(Stellar::KeyPair)
27
+ raise ArgumentError, "Bad :source_account"
26
28
  end
27
29
 
28
- op
30
+ body = Stellar::Operation::Body.new(*attributes[:body])
31
+
32
+ Stellar::Operation.new(
33
+ body: body,
34
+ source_account: source_account&.muxed_account
35
+ )
29
36
  end
30
37
 
31
38
  #
@@ -233,7 +240,7 @@ module Stellar
233
240
 
234
241
  def begin_sponsoring_future_reserves(sponsored:, **attributes)
235
242
  op = BeginSponsoringFutureReservesOp.new(
236
- sponsored_id: Stellar.KeyPair(sponsored).account_id
243
+ sponsored_id: KeyPair(sponsored).account_id
237
244
  )
238
245
 
239
246
  make(attributes.merge(body: [:begin_sponsoring_future_reserves, op]))
@@ -247,10 +254,10 @@ module Stellar
247
254
  def revoke_sponsorship(sponsored:, **attributes)
248
255
  key_fields = attributes.slice(:offer_id, :data_name, :balance_id, :asset, :signer)
249
256
  raise ArgumentError, "conflicting attributes: #{key_fields.keys.join(", ")}" if key_fields.size > 1
250
- account_id = Stellar.KeyPair(sponsored).account_id
257
+ account_id = KeyPair(sponsored).account_id
251
258
  key, value = key_fields.first
252
259
  op = if key == :signer
253
- RevokeSponsorshipOp.signer(account_id: account_id, signer_key: Stellar.SignerKey(value))
260
+ RevokeSponsorshipOp.signer(account_id: account_id, signer_key: SignerKey(value))
254
261
  else
255
262
  RevokeSponsorshipOp.ledger_key(LedgerKey.from(account_id: account_id, **key_fields))
256
263
  end
@@ -299,7 +306,7 @@ module Stellar
299
306
  op = ManageBuyOfferOp.new({
300
307
  buying: buying,
301
308
  selling: selling,
302
- amount: amount,
309
+ buy_amount: amount,
303
310
  price: price,
304
311
  offer_id: offer_id
305
312
  })
@@ -370,11 +377,31 @@ module Stellar
370
377
  }))
371
378
  end
372
379
 
380
+ # @param asset [Stellar::Asset]
381
+ # @param trustor [Stellar::KeyPair]
382
+ # @param flags [{String, Symbol, Stellar::TrustLineFlags => true, false}] flags to to set or clear
383
+ # @param source_account [Stellar::KeyPair] source account (default is `nil`, which will use the source account of transaction)
384
+ def set_trust_line_flags(asset:, trustor:, flags: {}, source_account: nil)
385
+ op = Stellar::SetTrustLineFlagsOp.new
386
+ op.trustor = KeyPair(trustor).account_id
387
+ op.asset = Asset(asset)
388
+ op.attributes = Stellar::TrustLineFlags.set_clear_masks(flags)
389
+
390
+ make(
391
+ source_account: source_account,
392
+ body: [:set_trust_line_flags, op]
393
+ )
394
+ end
395
+
396
+ # DEPRECATED in favor of `set_trustline_flags`
373
397
  #
374
398
  # Helper method to create a valid AllowTrustOp, wrapped
375
399
  # in the necessary XDR structs to be included within a
376
400
  # transactions `operations` array.
377
401
  #
402
+ # @deprecated Use `set_trustline_flags` operation
403
+ # See {https://github.com/stellar/stellar-protocol/blob/master/core/cap-0035.md#allow-trust-operation-1 CAP-35 description}
404
+ # for more details
378
405
  # @param [Hash] attributes the attributes to create the operation with
379
406
  # @option attributes [Stellar::KeyPair] :trustor
380
407
  # @option attributes [Stellar::Asset] :asset
@@ -386,7 +413,8 @@ module Stellar
386
413
  op = AllowTrustOp.new
387
414
 
388
415
  trustor = attributes[:trustor]
389
- authorize = attributes[:authorize]
416
+ # we handle booleans here for the backward compatibility
417
+ authorize = attributes[:authorize].yield_self { |value| value == true ? :full : value }
390
418
  asset = attributes[:asset]
391
419
  if asset.is_a?(Array)
392
420
  asset = Asset.send(*asset)
@@ -394,20 +422,21 @@ module Stellar
394
422
 
395
423
  raise ArgumentError, "Bad :trustor" unless trustor.is_a?(Stellar::KeyPair)
396
424
 
397
- op.authorize = case authorize
398
- when :none, false then 0 # we handle booleans here for the backward compatibility
399
- when :full, true then TrustLineFlags.authorized_flag.value
400
- when :maintain_liabilities then TrustLineFlags.authorized_to_maintain_liabilities_flag.value
425
+ allowed_flags = TRUST_LINE_FLAGS_MAPPING.slice(:full, :maintain_liabilities)
426
+
427
+ # we handle booleans here for the backward compatibility
428
+ op.authorize = if allowed_flags.key?(authorize)
429
+ allowed_flags[authorize].value
430
+ elsif [:none, false].include?(authorize)
431
+ 0
401
432
  else
402
433
  raise ArgumentError, "Bad :authorize, supported values: :full, :maintain_liabilities, :none"
403
434
  end
404
435
 
405
436
  raise ArgumentError, "Bad :asset" unless asset.type == Stellar::AssetType.asset_type_credit_alphanum4
406
437
 
407
- atc = AllowTrustOp::Asset.new(:asset_type_credit_alphanum4, asset.code)
408
-
409
438
  op.trustor = trustor.account_id
410
- op.asset = atc
439
+ op.asset = AssetCode.new(:asset_type_credit_alphanum4, asset.code)
411
440
 
412
441
  make(attributes.merge({
413
442
  body: [:allow_trust, op]
@@ -492,6 +521,47 @@ module Stellar
492
521
  }))
493
522
  end
494
523
 
524
+ def clawback(source_account:, from:, amount:)
525
+ asset, amount = get_asset_amount(amount)
526
+
527
+ if amount == 0
528
+ raise ArgumentError, "Amount can not be zero"
529
+ end
530
+
531
+ if amount < 0
532
+ raise ArgumentError, "Negative amount is not allowed"
533
+ end
534
+
535
+ op = ClawbackOp.new(
536
+ amount: amount,
537
+ from: from.muxed_account,
538
+ asset: asset
539
+ )
540
+
541
+ make({
542
+ source_account: source_account,
543
+ body: [:clawback, op]
544
+ })
545
+ end
546
+
547
+ # Helper method to create clawback claimable balance operation
548
+ #
549
+ # @param [Stellar::KeyPair] source_account the attributes to create the operation with
550
+ # @param [String] balance_id `ClaimableBalanceID`, serialized in hex
551
+ #
552
+ # @return [Stellar::Operation] the built operation
553
+ def clawback_claimable_balance(source_account:, balance_id:)
554
+ balance_id = Stellar::ClaimableBalanceID.from_xdr(balance_id, :hex)
555
+ op = ClawbackClaimableBalanceOp.new(balance_id: balance_id)
556
+
557
+ make(
558
+ source_account: source_account,
559
+ body: [:clawback_claimable_balance, op]
560
+ )
561
+ rescue XDR::ReadError
562
+ raise ArgumentError, "Claimable balance id '#{balance_id}' is invalid"
563
+ end
564
+
495
565
  private
496
566
 
497
567
  def get_asset_amount(values)
@@ -29,7 +29,7 @@ module Stellar
29
29
  end
30
30
 
31
31
  def to_envelope(*key_pairs)
32
- signatures = (key_pairs || []).map(&method(:sign_decorated))
32
+ signatures = key_pairs.map(&method(:sign_decorated))
33
33
 
34
34
  TransactionEnvelope.v1(signatures: signatures, tx: self)
35
35
  end
@@ -1,5 +1,7 @@
1
1
  module Stellar
2
2
  class TransactionBuilder
3
+ include Stellar::DSL
4
+
3
5
  attr_reader :source_account, :sequence_number, :base_fee, :time_bounds, :memo, :operations
4
6
 
5
7
  class << self
@@ -29,21 +31,20 @@ module Stellar
29
31
  base_fee: 100,
30
32
  time_bounds: nil,
31
33
  memo: nil,
34
+ enable_muxed_accounts: false,
32
35
  **_ # ignore any additional parameters without errors
33
36
  )
34
- raise ArgumentError, "Bad :source_account" unless source_account.is_a?(Stellar::KeyPair)
35
37
  raise ArgumentError, "Bad :sequence_number" unless sequence_number.is_a?(Integer) && sequence_number >= 0
36
38
  raise ArgumentError, "Bad :time_bounds" unless time_bounds.is_a?(Stellar::TimeBounds) || time_bounds.nil?
37
39
  raise ArgumentError, "Bad :base_fee" unless base_fee.is_a?(Integer) && base_fee >= 100
38
40
 
39
- @source_account = source_account
41
+ @source_account = Account(source_account)
40
42
  @sequence_number = sequence_number
41
43
  @base_fee = base_fee
42
44
  @time_bounds = time_bounds
45
+ @enable_muxed_accounts = enable_muxed_accounts
43
46
 
44
- if time_bounds.nil?
45
- set_timeout(0)
46
- end
47
+ set_timeout(0) if time_bounds.nil?
47
48
 
48
49
  @memo = make_memo(memo)
49
50
  @operations = []
@@ -59,7 +60,7 @@ module Stellar
59
60
  end
60
61
 
61
62
  attrs = {
62
- source_account: @source_account.muxed_account,
63
+ source_account: source_muxed_account,
63
64
  fee: @base_fee * @operations.length,
64
65
  seq_num: @sequence_number,
65
66
  time_bounds: @time_bounds,
@@ -90,7 +91,7 @@ module Stellar
90
91
  end
91
92
 
92
93
  Stellar::FeeBumpTransaction.new(
93
- fee_source: @source_account.muxed_account,
94
+ fee_source: source_muxed_account,
94
95
  fee: @base_fee * (inner_ops.length + 1),
95
96
  inner_tx: Stellar::FeeBumpTransaction::InnerTx.new(:envelope_type_tx, inner_txe.v1!),
96
97
  ext: Stellar::FeeBumpTransaction::Ext.new(0)
@@ -162,5 +163,17 @@ module Stellar
162
163
  raise ArgumentError, "Bad :memo"
163
164
  end
164
165
  end
166
+
167
+ def source_muxed_account
168
+ if with_muxed_accounts?
169
+ @source_account.muxed_account
170
+ else
171
+ @source_account.base_account
172
+ end
173
+ end
174
+
175
+ def with_muxed_accounts?
176
+ @enable_muxed_accounts
177
+ end
165
178
  end
166
179
  end
@@ -1,13 +1,7 @@
1
1
  module Stellar
2
2
  class TransactionEnvelope
3
- # Delegates any undefined method to the currently set arm
4
- def method_missing(method, *args, &block)
5
- value&.public_send(method, *args) || super
6
- end
7
-
8
- def respond_to_missing?(method, include_private = false)
9
- %w[tx signatures].include?(method) || super
10
- end
3
+ delegate :tx, :signatures, to: :value
4
+ delegate :hash, to: :tx
11
5
 
12
6
  # Checks to ensure that every signature for the envelope is
13
7
  # a valid signature of one of the provided `key_pairs`
@@ -19,23 +13,19 @@ module Stellar
19
13
  #
20
14
  # @return [Boolean] true if all signatures are from the provided key_pairs and validly sign the tx's hash
21
15
  def signed_correctly?(*key_pairs)
22
- hash = tx.hash
23
16
  return false if signatures.empty?
24
17
 
25
- key_index = key_pairs.index_by(&:signature_hint)
18
+ tx_hash = tx.hash
19
+ keys_by_hint = key_pairs.index_by(&:signature_hint)
26
20
 
27
21
  signatures.all? do |sig|
28
- key_pair = key_index[sig.hint]
22
+ key_pair = keys_by_hint[sig.hint]
29
23
  break false if key_pair.nil?
30
24
 
31
- key_pair.verify(sig.signature, hash)
25
+ key_pair.verify(sig.signature, tx_hash)
32
26
  end
33
27
  end
34
28
 
35
- def hash
36
- Digest::SHA256.digest(to_xdr)
37
- end
38
-
39
29
  def merge(other)
40
30
  merged_tx = tx.merge(other.tx)
41
31
  merged_tx.signatures = [signatures, other.signatures]