xrpl-ruby 0.6.1 → 0.8.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +155 -0
- data/README.md +99 -3
- data/lib/binary-codec/binary_codec.rb +22 -1
- data/lib/binary-codec/enums/definitions.json +3856 -456
- data/lib/binary-codec/enums/definitions.rb +46 -1
- data/lib/binary-codec/types/amount.rb +5 -2
- data/lib/binary-codec/types/currency.rb +8 -1
- data/lib/binary-codec/types/issue.rb +68 -23
- data/lib/binary-codec/types/number.rb +196 -0
- data/lib/binary-codec/types/serialized_type.rb +5 -2
- data/lib/binary-codec/types/st_array.rb +17 -19
- data/lib/binary-codec/types/st_object.rb +15 -13
- data/lib/binary-codec/types/uint.rb +55 -44
- data/lib/binary-codec/types/xchain_bridge.rb +17 -2
- data/lib/wallet/wallet.rb +32 -0
- data/lib/xrpl/client.rb +47 -9
- data/lib/xrpl/fee.rb +82 -0
- data/lib/xrpl/ledger_entry.rb +53 -0
- data/lib/xrpl/model.rb +239 -0
- data/lib/xrpl/transaction.rb +53 -0
- data/lib/xrpl/version.rb +1 -1
- data/lib/xrpl-ruby.rb +5 -0
- metadata +7 -2
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
module BinaryCodec
|
|
4
4
|
|
|
5
5
|
class Uint < ComparableSerializedType
|
|
6
|
+
# UInt64 fields that rippled renders in base 10 rather than as hex. They
|
|
7
|
+
# hold MPToken amounts, where a hex string would be a needless surprise.
|
|
8
|
+
BASE10_UINT64_FIELDS = %w[
|
|
9
|
+
MaximumAmount
|
|
10
|
+
OutstandingAmount
|
|
11
|
+
MPTAmount
|
|
12
|
+
LockedAmount
|
|
13
|
+
ConfidentialOutstandingAmount
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
6
16
|
# Returns the width of the Uint type in bytes.
|
|
7
17
|
# @return [Integer] The width.
|
|
8
18
|
def self.width
|
|
@@ -20,22 +30,14 @@ module BinaryCodec
|
|
|
20
30
|
return value if value.is_a?(self)
|
|
21
31
|
|
|
22
32
|
if value.is_a?(String)
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return new(int_to_bytes(ledger_entry_types[value], width))
|
|
32
|
-
end
|
|
33
|
-
elsif self == Uint8
|
|
34
|
-
transaction_results = Definitions.instance.instance_variable_get(:@transaction_results)
|
|
35
|
-
if transaction_results&.key?(value)
|
|
36
|
-
return new(int_to_bytes(transaction_results[value], width))
|
|
37
|
-
end
|
|
38
|
-
end
|
|
33
|
+
# Names for the fields that carry one: TransactionType and
|
|
34
|
+
# LedgerEntryType (UInt16), TransactionResult (UInt8) and the
|
|
35
|
+
# PermissionValue of a DelegateSet (UInt32). A name is never a valid
|
|
36
|
+
# number, so the lookup cannot capture a numeric string.
|
|
37
|
+
code = NAMED_VALUES.fetch(self, [])
|
|
38
|
+
.filter_map { |table| Definitions.instance.public_send(table)[value] }
|
|
39
|
+
.first
|
|
40
|
+
return new(int_to_bytes(code, width)) if code
|
|
39
41
|
|
|
40
42
|
# Handle hex strings or numeric strings
|
|
41
43
|
if valid_hex?(value) && value.length == self.width * 2
|
|
@@ -68,36 +70,28 @@ module BinaryCodec
|
|
|
68
70
|
# Returns the JSON representation of the Uint.
|
|
69
71
|
# @return [Integer, String] The value.
|
|
70
72
|
def to_json(_definitions = nil, _field_name = nil)
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if
|
|
77
|
-
name = transaction_types.key(val)
|
|
78
|
-
return name if name
|
|
79
|
-
end
|
|
80
|
-
elsif _field_name == 'LedgerEntryType'
|
|
81
|
-
val = value_of
|
|
82
|
-
ledger_entry_types = Definitions.instance.instance_variable_get(:@ledger_entry_types)
|
|
83
|
-
if ledger_entry_types
|
|
84
|
-
name = ledger_entry_types.key(val)
|
|
85
|
-
return name if name
|
|
86
|
-
end
|
|
87
|
-
elsif _field_name == 'TransactionResult'
|
|
88
|
-
val = value_of
|
|
89
|
-
transaction_results = Definitions.instance.instance_variable_get(:@transaction_results)
|
|
90
|
-
if transaction_results
|
|
91
|
-
name = transaction_results.key(val)
|
|
92
|
-
return name if name
|
|
93
|
-
end
|
|
73
|
+
# The fields that carry a name render it, the way rippled does. A code
|
|
74
|
+
# without a name falls through and renders as the number.
|
|
75
|
+
table = NAMED_FIELDS[_field_name]
|
|
76
|
+
if table
|
|
77
|
+
name = Definitions.instance.public_send(table).key(value_of)
|
|
78
|
+
return name if name
|
|
94
79
|
end
|
|
95
80
|
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
81
|
+
# rippled renders the narrow unsigned integers as JSON numbers and UInt64
|
|
82
|
+
# as a 16 digit hex string, because a UInt64 does not survive a round trip
|
|
83
|
+
# through a JSON number. The MPToken amount fields are the exception to
|
|
84
|
+
# that exception: they are UInt64 but carry a base 10 string.
|
|
85
|
+
#
|
|
86
|
+
# Everything wider than 8 bytes (Uint96 and up) is hash-like and stays
|
|
87
|
+
# hex. Do not widen the numeric branch to cover it.
|
|
88
|
+
val = value_of
|
|
89
|
+
return val if self.class.width < 8
|
|
90
|
+
return val.to_s if self.class.width == 8 && BASE10_UINT64_FIELDS.include?(_field_name)
|
|
91
|
+
|
|
92
|
+
# Hex is unsigned, so a negative signed value has to wrap first.
|
|
93
|
+
val += (1 << (self.class.width * 8)) if val < 0
|
|
94
|
+
val.to_s(16).upcase.rjust(self.class.width * 2, '0')
|
|
101
95
|
end
|
|
102
96
|
# @param other [Uint] The other Uint to compare to.
|
|
103
97
|
# @return [Integer] Comparison result (-1, 0, or 1).
|
|
@@ -213,4 +207,21 @@ module BinaryCodec
|
|
|
213
207
|
end
|
|
214
208
|
end
|
|
215
209
|
|
|
210
|
+
class Uint
|
|
211
|
+
# Which name tables a width accepts on the way in ...
|
|
212
|
+
NAMED_VALUES = {
|
|
213
|
+
Uint16 => %i[transaction_types ledger_entry_types],
|
|
214
|
+
Uint8 => %i[transaction_results],
|
|
215
|
+
Uint32 => %i[delegatable_permissions]
|
|
216
|
+
}.freeze
|
|
217
|
+
|
|
218
|
+
# ... and which field renders its value by name on the way out.
|
|
219
|
+
NAMED_FIELDS = {
|
|
220
|
+
'TransactionType' => :transaction_types,
|
|
221
|
+
'LedgerEntryType' => :ledger_entry_types,
|
|
222
|
+
'TransactionResult' => :transaction_results,
|
|
223
|
+
'PermissionValue' => :delegatable_permissions
|
|
224
|
+
}.freeze
|
|
225
|
+
end
|
|
226
|
+
|
|
216
227
|
end
|
|
@@ -15,9 +15,9 @@ module BinaryCodec
|
|
|
15
15
|
|
|
16
16
|
if value.is_a?(::Hash)
|
|
17
17
|
bytes = []
|
|
18
|
-
bytes.concat(
|
|
18
|
+
bytes.concat(door_bytes(value['LockingChainDoor']))
|
|
19
19
|
bytes.concat(Issue.from(value['LockingChainIssue']).to_bytes)
|
|
20
|
-
bytes.concat(
|
|
20
|
+
bytes.concat(door_bytes(value['IssuingChainDoor']))
|
|
21
21
|
bytes.concat(Issue.from(value['IssuingChainIssue']).to_bytes)
|
|
22
22
|
return XChainBridge.new(bytes)
|
|
23
23
|
end
|
|
@@ -25,10 +25,23 @@ module BinaryCodec
|
|
|
25
25
|
raise StandardError, "Cannot construct XChainBridge from #{value.class}"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# An account inside a bridge carries the same length prefix as a
|
|
29
|
+
# standalone AccountID field. Leaving it off produces bytes that decode
|
|
30
|
+
# as something else entirely, which is why every XChain fixture failed
|
|
31
|
+
# to serialise.
|
|
32
|
+
DOOR_LENGTH_PREFIX = 0x14
|
|
33
|
+
|
|
34
|
+
def self.door_bytes(account)
|
|
35
|
+
[DOOR_LENGTH_PREFIX] + AccountId.from(account).to_bytes
|
|
36
|
+
end
|
|
37
|
+
private_class_method :door_bytes
|
|
38
|
+
|
|
28
39
|
def self.from_parser(parser, _hint = nil)
|
|
29
40
|
bytes = []
|
|
41
|
+
bytes.concat(parser.read(1)) # length prefix
|
|
30
42
|
bytes.concat(parser.read(20)) # LockingChainDoor
|
|
31
43
|
bytes.concat(Issue.from_parser(parser, 40).to_bytes) # LockingChainIssue
|
|
44
|
+
bytes.concat(parser.read(1)) # length prefix
|
|
32
45
|
bytes.concat(parser.read(20)) # IssuingChainDoor
|
|
33
46
|
bytes.concat(Issue.from_parser(parser, 40).to_bytes) # IssuingChainIssue
|
|
34
47
|
XChainBridge.new(bytes)
|
|
@@ -37,8 +50,10 @@ module BinaryCodec
|
|
|
37
50
|
def to_json(_definitions = nil, _field_name = nil)
|
|
38
51
|
parser = BinaryParser.new(to_hex)
|
|
39
52
|
result = {}
|
|
53
|
+
parser.read(1) # length prefix
|
|
40
54
|
result['LockingChainDoor'] = AccountId.from_parser(parser).to_json
|
|
41
55
|
result['LockingChainIssue'] = Issue.from_parser(parser, 40).to_json
|
|
56
|
+
parser.read(1) # length prefix
|
|
42
57
|
result['IssuingChainDoor'] = AccountId.from_parser(parser).to_json
|
|
43
58
|
result['IssuingChainIssue'] = Issue.from_parser(parser, 40).to_json
|
|
44
59
|
result
|
data/lib/wallet/wallet.rb
CHANGED
|
@@ -162,6 +162,38 @@ module Wallet
|
|
|
162
162
|
@key_pairs.verify(bytes_to_hex(signing_data), signature, @public_key)
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
+
# Signs a payment channel claim: the promise that +amount+ drops of the
|
|
166
|
+
# channel's funds may be claimed. The signature goes into a
|
|
167
|
+
# PaymentChannelClaim transaction as its Signature.
|
|
168
|
+
#
|
|
169
|
+
# @param channel [String] the channel ID, 64 hex characters
|
|
170
|
+
# @param amount [String, Integer] the amount in drops
|
|
171
|
+
# @return [String] the signature as hex
|
|
172
|
+
def sign_payment_channel_claim(channel, amount)
|
|
173
|
+
data = BinaryCodec.signing_claim_data(channel: channel, amount: amount)
|
|
174
|
+
@key_pairs.sign(bytes_to_hex(data), @private_key, @algorithm)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Verifies a payment channel claim against this wallet's public key.
|
|
178
|
+
#
|
|
179
|
+
# @param channel [String] the channel ID, 64 hex characters
|
|
180
|
+
# @param amount [String, Integer] the amount in drops
|
|
181
|
+
# @param signature [String] the signature as hex
|
|
182
|
+
# @return [Boolean] whether the claim was signed by this wallet
|
|
183
|
+
def verify_payment_channel_claim(channel, amount, signature)
|
|
184
|
+
self.class.verify_payment_channel_claim(channel, amount, signature, @public_key)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Verifies a payment channel claim against any public key - the channel's
|
|
188
|
+
# PublicKey, as the receiving side has it.
|
|
189
|
+
#
|
|
190
|
+
# @param public_key [String] the public key as hex
|
|
191
|
+
# @return [Boolean] whether the claim was signed with that key
|
|
192
|
+
def self.verify_payment_channel_claim(channel, amount, signature, public_key)
|
|
193
|
+
data = BinaryCodec.signing_claim_data(channel: channel, amount: amount)
|
|
194
|
+
KeyPairs::KeyPairs.new.verify(bytes_to_hex(data), signature, public_key)
|
|
195
|
+
end
|
|
196
|
+
|
|
165
197
|
# Derives the X-address for this wallet.
|
|
166
198
|
# @param tag [Integer, false, nil] The destination tag.
|
|
167
199
|
# @param test_network [Boolean] Whether the address is for a test network.
|
data/lib/xrpl/client.rb
CHANGED
|
@@ -35,17 +35,35 @@ module XRPL
|
|
|
35
35
|
|
|
36
36
|
attr_reader :url, :connection
|
|
37
37
|
|
|
38
|
+
# Accepts a transaction as a plain Hash or as an XRPL::Transaction and
|
|
39
|
+
# hands back the Hash the rest of the pipeline works with. Anything else
|
|
40
|
+
# is passed through untouched, so a pre-signed blob still reaches submit.
|
|
41
|
+
#
|
|
42
|
+
# @param transaction [Hash, XRPL::Transaction, Object]
|
|
43
|
+
# @return [Hash, Object]
|
|
44
|
+
def self.to_transaction_hash(transaction)
|
|
45
|
+
return transaction if transaction.is_a?(Hash)
|
|
46
|
+
return transaction.to_h if transaction.is_a?(XRPL::Transaction)
|
|
47
|
+
|
|
48
|
+
transaction
|
|
49
|
+
end
|
|
50
|
+
|
|
38
51
|
# @param url [String, Symbol] a network alias (:testnet/:mainnet/:devnet) or a WebSocket URL.
|
|
39
52
|
# @param logger [Logger, nil] optional logger for diagnostic messages. When nil
|
|
40
53
|
# (the default), the client stays silent — a library must not write to the
|
|
41
54
|
# host application's stdout uninvited. Pass e.g. +Logger.new($stdout)+ to opt in.
|
|
42
|
-
|
|
55
|
+
# @param max_fee_drops [Integer] cap for the fee autofill computes, so a
|
|
56
|
+
# spike in the open ledger fee cannot burn an account. Transactions that
|
|
57
|
+
# cost an owner reserve (AccountDelete, AMMCreate, VaultCreate) are not
|
|
58
|
+
# capped.
|
|
59
|
+
def initialize(url, logger: nil, max_fee_drops: Fee::MAX_FEE_DROPS)
|
|
43
60
|
@url = resolve_url(url)
|
|
44
61
|
@connection = nil
|
|
45
62
|
@requests = {}
|
|
46
63
|
@open = false
|
|
47
64
|
@ready_queue = Queue.new
|
|
48
65
|
@logger = logger
|
|
66
|
+
@max_fee_drops = max_fee_drops
|
|
49
67
|
end
|
|
50
68
|
|
|
51
69
|
# Opens the WebSocket connection.
|
|
@@ -306,13 +324,17 @@ module XRPL
|
|
|
306
324
|
# Fills in the fields a transaction needs before signing: +Sequence+, +Fee+
|
|
307
325
|
# and +LastLedgerSequence+. Existing values are never overwritten.
|
|
308
326
|
#
|
|
309
|
-
#
|
|
327
|
+
# The fee follows the transaction type (see XRPL::Fee): an EscrowFinish
|
|
328
|
+
# pays for its Fulfillment, AccountDelete, AMMCreate and VaultCreate cost
|
|
329
|
+
# the owner reserve, a Batch pays for its inner transactions.
|
|
330
|
+
#
|
|
331
|
+
# @param transaction [Hash, XRPL::Transaction] the transaction to complete.
|
|
310
332
|
# @param signers_count [Integer] number of signatures for multisign fee scaling.
|
|
311
333
|
# @return [Hash] a copy of the transaction with the missing fields filled in.
|
|
312
334
|
def autofill(transaction, signers_count: 0)
|
|
313
|
-
tx = transaction.dup
|
|
335
|
+
tx = self.class.to_transaction_hash(transaction).dup
|
|
314
336
|
tx['Sequence'] ||= fetch_sequence(tx.fetch('Account'))
|
|
315
|
-
tx['Fee'] ||= calculate_fee(signers_count)
|
|
337
|
+
tx['Fee'] ||= calculate_fee(tx, signers_count)
|
|
316
338
|
tx['LastLedgerSequence'] ||= current_ledger_index + LEDGER_OFFSET
|
|
317
339
|
tx
|
|
318
340
|
end
|
|
@@ -399,7 +421,8 @@ module XRPL
|
|
|
399
421
|
def prepare_for_submit(transaction, wallet:, autofill:)
|
|
400
422
|
raise ArgumentError, 'wallet: is required to sign the transaction' if wallet.nil?
|
|
401
423
|
|
|
402
|
-
tx =
|
|
424
|
+
tx = self.class.to_transaction_hash(transaction)
|
|
425
|
+
tx = tx.dup if tx.is_a?(Hash)
|
|
403
426
|
tx = autofill(tx) if autofill && tx.is_a?(Hash)
|
|
404
427
|
|
|
405
428
|
signed = wallet.sign(tx)
|
|
@@ -426,10 +449,14 @@ module XRPL
|
|
|
426
449
|
Integer(sequence)
|
|
427
450
|
end
|
|
428
451
|
|
|
429
|
-
def calculate_fee(signers_count)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
452
|
+
def calculate_fee(tx, signers_count)
|
|
453
|
+
Fee.calculate(
|
|
454
|
+
tx,
|
|
455
|
+
base_fee: base_fee_drops,
|
|
456
|
+
signers_count: signers_count.to_i,
|
|
457
|
+
max_fee: @max_fee_drops,
|
|
458
|
+
owner_reserve: -> { owner_reserve_drops }
|
|
459
|
+
).to_s
|
|
433
460
|
end
|
|
434
461
|
|
|
435
462
|
def base_fee_drops
|
|
@@ -441,6 +468,17 @@ module XRPL
|
|
|
441
468
|
DEFAULT_FEE_DROPS
|
|
442
469
|
end
|
|
443
470
|
|
|
471
|
+
# The owner reserve (reserve_inc), which AccountDelete, AMMCreate and
|
|
472
|
+
# VaultCreate cost instead of a fee. There is no sensible default: a
|
|
473
|
+
# guess would be rejected or overpay by orders of magnitude.
|
|
474
|
+
def owner_reserve_drops
|
|
475
|
+
response = request_with_retry('server_state')
|
|
476
|
+
reserve = response.dig('result', 'state', 'validated_ledger', 'reserve_inc')
|
|
477
|
+
raise TransactionError, 'Could not determine the owner reserve from server_state' unless reserve
|
|
478
|
+
|
|
479
|
+
Integer(reserve)
|
|
480
|
+
end
|
|
481
|
+
|
|
444
482
|
def current_ledger_index
|
|
445
483
|
response = request_with_retry('ledger_current')
|
|
446
484
|
index = response.dig('result', 'ledger_current_index')
|
data/lib/xrpl/fee.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XRPL
|
|
4
|
+
# The fee a transaction needs, in drops, by transaction type.
|
|
5
|
+
#
|
|
6
|
+
# rippled charges most transactions the network's base fee, but not all of
|
|
7
|
+
# them: an EscrowFinish pays for the size of its Fulfillment, AccountDelete,
|
|
8
|
+
# AMMCreate and VaultCreate cost an owner reserve rather than a fee, a Batch
|
|
9
|
+
# pays for its inner transactions, and the confidential MPT transactions
|
|
10
|
+
# cost ten base fees. Multisigning adds one base fee per signature. The
|
|
11
|
+
# rules are the ones xrpl.js applies in its autofill.
|
|
12
|
+
#
|
|
13
|
+
# This module is pure: it takes the base fee and, when a type needs it, a
|
|
14
|
+
# way to get the owner reserve. XRPL::Client wires it to the ledger.
|
|
15
|
+
module Fee
|
|
16
|
+
# Where an ordinary fee is capped, so that a spike in the open ledger fee
|
|
17
|
+
# cannot burn an account. The reserve-priced types are not capped: their
|
|
18
|
+
# cost is what it is.
|
|
19
|
+
MAX_FEE_DROPS = 2_000_000
|
|
20
|
+
|
|
21
|
+
# Types whose cost is the owner reserve (reserve_inc), not a fee.
|
|
22
|
+
RESERVE_PRICED = %w[AccountDelete AMMCreate VaultCreate].freeze
|
|
23
|
+
|
|
24
|
+
# rippled's kCONFIDENTIAL_FEE_MULTIPLIER: extra base fees on top of the
|
|
25
|
+
# standard one, so ten in total.
|
|
26
|
+
CONFIDENTIAL_MPT_MULTIPLIER = 9
|
|
27
|
+
|
|
28
|
+
CONFIDENTIAL_MPT_TYPES = %w[
|
|
29
|
+
ConfidentialMPTConvert ConfidentialMPTConvertBack ConfidentialMPTSend
|
|
30
|
+
ConfidentialMPTClawback ConfidentialMPTMergeInbox
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# @param transaction [Hash, XRPL::Transaction] the transaction, with its
|
|
36
|
+
# TransactionType
|
|
37
|
+
# @param base_fee [Integer] the network's base fee in drops
|
|
38
|
+
# @param signers_count [Integer] number of signatures for multisign scaling
|
|
39
|
+
# @param max_fee [Integer] cap for ordinary fees
|
|
40
|
+
# @param owner_reserve [Integer, #call, nil] the owner reserve in drops,
|
|
41
|
+
# or something that fetches it; only consulted for the reserve-priced
|
|
42
|
+
# types
|
|
43
|
+
# @return [Integer] the fee in drops, rounded up
|
|
44
|
+
def calculate(transaction, base_fee:, signers_count: 0, max_fee: MAX_FEE_DROPS, owner_reserve: nil)
|
|
45
|
+
tx = transaction.respond_to?(:to_h) ? transaction.to_h : transaction
|
|
46
|
+
type = tx['TransactionType'] || tx[:TransactionType]
|
|
47
|
+
base_fee = Integer(base_fee)
|
|
48
|
+
|
|
49
|
+
fee = Rational(base_fee)
|
|
50
|
+
reserve_priced = RESERVE_PRICED.include?(type)
|
|
51
|
+
|
|
52
|
+
if type == 'EscrowFinish' && tx['Fulfillment']
|
|
53
|
+
# Base fee × (33 + Fulfillment size in bytes / 16)
|
|
54
|
+
bytes = (tx['Fulfillment'].to_s.length + 1) / 2
|
|
55
|
+
fee = base_fee * (33 + Rational(bytes, 16))
|
|
56
|
+
elsif reserve_priced
|
|
57
|
+
fee = Rational(resolve_owner_reserve(owner_reserve, type))
|
|
58
|
+
elsif type == 'Batch'
|
|
59
|
+
inner = Array(tx['RawTransactions']).sum(0r) do |raw|
|
|
60
|
+
calculate(raw['RawTransaction'] || raw, base_fee: base_fee, max_fee: max_fee,
|
|
61
|
+
owner_reserve: owner_reserve)
|
|
62
|
+
end
|
|
63
|
+
fee = base_fee * 2 + inner
|
|
64
|
+
elsif CONFIDENTIAL_MPT_TYPES.include?(type)
|
|
65
|
+
fee += base_fee * CONFIDENTIAL_MPT_MULTIPLIER
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Multisigned: base fee × (1 + number of signatures)
|
|
69
|
+
fee += base_fee * signers_count if signers_count.to_i.positive?
|
|
70
|
+
|
|
71
|
+
fee = [fee, Rational(max_fee)].min unless reserve_priced
|
|
72
|
+
fee.ceil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def resolve_owner_reserve(owner_reserve, type)
|
|
76
|
+
reserve = owner_reserve.respond_to?(:call) ? owner_reserve.call : owner_reserve
|
|
77
|
+
raise ArgumentError, "#{type} costs the owner reserve, which was not given" if reserve.nil?
|
|
78
|
+
|
|
79
|
+
Integer(reserve)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XRPL
|
|
4
|
+
# A ledger entry - AccountRoot, RippleState, Offer, Escrow and the other
|
|
5
|
+
# objects that make up the ledger's state - built from LEDGER_ENTRY_FORMATS
|
|
6
|
+
# in definitions.json, the same way XRPL::Transaction is built from
|
|
7
|
+
# TRANSACTION_FORMATS.
|
|
8
|
+
#
|
|
9
|
+
# entry = XRPL::LedgerEntry.from(response.dig('result', 'node'))
|
|
10
|
+
# entry.class # => XRPL::LedgerEntry::AccountRoot
|
|
11
|
+
# entry.balance # => "370000000"
|
|
12
|
+
# entry.flag?(:lsf_default_ripple)
|
|
13
|
+
# entry.flag_names # => ["lsfDefaultRipple"]
|
|
14
|
+
#
|
|
15
|
+
# Where rippled hands out the entry's own hash under "index" (ledger_entry,
|
|
16
|
+
# account_objects, ledger_data), that value is kept as #index; it is not a
|
|
17
|
+
# field of the object and is left out of #to_h.
|
|
18
|
+
class LedgerEntry < Model
|
|
19
|
+
# Fields every ledger entry carries, regardless of type.
|
|
20
|
+
COMMON_FORMAT = DEFINITIONS['LEDGER_ENTRY_FORMATS'].fetch('common').freeze
|
|
21
|
+
|
|
22
|
+
# LEDGER_ENTRY_FLAGS keys DirectoryNode's flags under rippled's short
|
|
23
|
+
# name for the type.
|
|
24
|
+
FLAG_ALIASES = { 'DirectoryNode' => 'DirNode' }.freeze
|
|
25
|
+
|
|
26
|
+
# The entry's hash, as rippled reports it under "index".
|
|
27
|
+
attr_accessor :index
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
# The ledger's name for this entry type, e.g. "AccountRoot".
|
|
31
|
+
alias ledger_entry_type type_name
|
|
32
|
+
|
|
33
|
+
def type_field
|
|
34
|
+
'LedgerEntryType'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def label
|
|
38
|
+
'ledger entry'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def []=(name, value)
|
|
43
|
+
if name.to_s == 'index'
|
|
44
|
+
@index = value
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
define_types!(DEFINITIONS['LEDGER_ENTRY_FORMATS'], DEFINITIONS['LEDGER_ENTRY_FLAGS'], FLAG_ALIASES)
|
|
52
|
+
end
|
|
53
|
+
end
|