stellar-base 0.24.0 → 0.28.0

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 (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]