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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b0269ba7fe6c319fca687c0a063389a358260f4c84bb5ae293f1fb6f8215b0f
4
- data.tar.gz: ca8099d5daaa7f3b4e3c89cd3ea13870da4f15b60cbaeb08809edb36547121f3
3
+ metadata.gz: cb8b3e628ddf6a9d9a7e329e6e8c51e4b0cd4f872eb609e589d78795aa9f4782
4
+ data.tar.gz: 2f5b18b34cb386901a8ad1a7549f7cca241facce58c0841f36a10a4c200413cc
5
5
  SHA512:
6
- metadata.gz: a63151ad5c6885136417783f1234d6af8e32ea8346ce514430c8c65458dae0a0917cae0f8c2f1f7111c623e7e8098cc0b96643fbebbfd60554cd86f9c3cbd847
7
- data.tar.gz: f32401a94218f1ae9621c4ba21f64c994b08f3d3128f2e2b159175c2aad195d8f7050ca094454ad084ad5c4aef80a9e71750401462a541b1f346b12bb581a05b
6
+ metadata.gz: f066a56166ec0aa81ec3acb34106d504962a6e1cf2cee89eb13103719467f9ded35a34d432d9901c8a81f11b7dedc93311d8ebd184dc21564585163536e18558
7
+ data.tar.gz: 97397fe795b08eed3c56111b876d5476da705e2577c635f12e6aeee9ebc5ef86106fd44e525dc67c3716c11472b273bd4235764d9df2b7bf8bcc3e0d152078a7
data/CHANGELOG.md CHANGED
@@ -5,6 +5,161 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.0] - 2026-09-24
9
+
10
+ The codec now targets **rippled 3.4.0** (released 2026-09-17) and passes the
11
+ ripple-binary-codec 2.11.0 fixture set: 39 transactions and 263 ledger
12
+ entries, both directions (302 cases; 287 before). Measured before the change:
13
+ transactions 38/39, accountState 262/263 decode.
14
+
15
+ ### Breaking
16
+
17
+ - **`definitions.json` is rippled 3.4.0.** It is what a 3.4.0 node reports
18
+ through `server_definitions` (s1.ripple.com), with the node's own digest in
19
+ `hash`. FIELDS 381 → 357: the 27 `Hook*` fields plus `EmitGeneration` and
20
+ `EmittedTxn` are gone, because rippled dropped Hooks; a blob carrying one
21
+ of them no longer decodes. Added: `VaultKind`, `SubscriptionDate`,
22
+ `RedemptionDate`, `LEVersion`, `ContractResult` (LendingProtocolV1_1,
23
+ closed-ended vaults), and `CredentialIDs` on `VaultWithdraw` and
24
+ `LoanBrokerCoverWithdraw`. ripple-binary-codec's `main` additionally
25
+ carries four `*KeyEpoch` fields from rippled's development branch; they
26
+ are in no release and deliberately not here.
27
+ - **`PermissionValue` decodes to its name.** A `DelegateSet` permission is
28
+ a UInt32 that rippled renders by name: a transaction type as its code plus
29
+ one (`"Payment"` = 1), the twelve granular permissions from 65537
30
+ (`"TrustlineAuthorize"`). Encoding accepts the names; they were silently
31
+ serialised as 0 before.
32
+
33
+ ### Added
34
+
35
+ - **Ledger entry models** — `XRPL::LedgerEntry::AccountRoot`,
36
+ `::RippleState`, `::Offer` and the other 28 types, generated from
37
+ `LEDGER_ENTRY_FORMATS` the way the transaction models are generated from
38
+ `TRANSACTION_FORMATS`. `XRPL::LedgerEntry.from(node)` builds the right
39
+ class from anything `ledger_entry`, `account_objects` or `ledger_data`
40
+ returns and keeps rippled's `index` alongside. Every `lsf` flag is a
41
+ constant on its type. All 263 reference ledger entries round-trip through
42
+ their model unchanged.
43
+ - **Flag helpers** on transactions and ledger entries: `#flag?` takes the
44
+ ledger's name, the constant's name or the bit, `#flag_names` lists what
45
+ is set.
46
+ - **Fees by transaction type.** `autofill` now charges what rippled charges:
47
+ `EscrowFinish` pays for its `Fulfillment` (base × (33 + bytes / 16)),
48
+ `AccountDelete`, `AMMCreate` and `VaultCreate` cost the owner reserve
49
+ from `server_state`, a `Batch` pays two base fees plus those of its inner
50
+ transactions, the confidential MPT transactions cost ten base fees, and
51
+ multisigning adds one base fee per signature. Ordinary fees are capped at
52
+ 2 XRP (`Client.new(url, max_fee_drops:)`); the reserve-priced types are
53
+ not. The rules are a pure module, `XRPL::Fee.calculate`, and the same
54
+ ones xrpl.js applies. Before, every transaction paid the base fee, so an
55
+ AccountDelete or an EscrowFinish with a fulfillment could not be
56
+ autofilled.
57
+ - **Payment channel claims.** `Wallet#sign_payment_channel_claim(channel,
58
+ drops)` and `#verify_payment_channel_claim`, plus
59
+ `Wallet.verify_payment_channel_claim(..., public_key)` for the receiving
60
+ side; `BinaryCodec.signing_claim_data` underneath. Checked against the
61
+ xrpl.js signature vector byte for byte.
62
+ - The hash prefixes for batch, counterparty and sponsor signatures
63
+ (`fixCleanup3_4_0`) are in `BinaryCodec::HASH_PREFIX`; the signing
64
+ helpers for them are not written yet.
65
+ - `Definitions#delegatable_permissions`, and readers for the transaction
66
+ type, ledger entry type and result tables the codec resolves names
67
+ against.
68
+
69
+ ### Changed
70
+
71
+ - `XRPL::Transaction` is built on a new `XRPL::Model` base shared with
72
+ `XRPL::LedgerEntry`. Its public surface is unchanged;
73
+ `Transaction::ValidationError` is now `Model::ValidationError`, reachable
74
+ under both names. A model rejects a hash whose type field contradicts the
75
+ class (`Payment.new('TransactionType' => 'TrustSet')`), where it used to
76
+ keep the foreign value and serialise a TrustSet.
77
+ - The transaction models pick up the 3.4.0 fields without a code change:
78
+ `VaultCreate.new(vault_kind:, subscription_date:, redemption_date:)`.
79
+ - A parity spec that pins `definitions.json` to the 3.4.0 digest and fails
80
+ if a Hook or development-branch field comes back.
81
+
82
+ ### Fixed
83
+
84
+ - `ConfidentialOutstandingAmount` renders in base 10 like the other MPToken
85
+ amounts, not as a 16 digit hex string.
86
+ - `Definitions#get_field_instance` returns `nil` for an unknown field name.
87
+ Serialising a hash with a foreign key now fails with "Field x is not
88
+ defined" instead of a `NoMethodError` on `nil`.
89
+
90
+ ## [0.7.0] - 2026-08-26
91
+
92
+ The binary codec now passes the full ripple-binary-codec reference fixture set
93
+ (287 cases, both directions). It previously passed none of them on decode: the
94
+ fixtures had been vendored in `spec/binary-codec/fixtures/` for a long time
95
+ without any spec loading them, so the suite reported green while decoding
96
+ 0 of 261 ledger objects.
97
+
98
+ ### Breaking
99
+
100
+ - **`binary_to_json` returns different types.** UInt8/16/32 fields come back as
101
+ numbers instead of zero-padded hex strings (`Sequence` is `1`, not
102
+ `"00000001"`), and native XRP amounts come back as strings instead of
103
+ integers (`Balance` is `"370000000"`, not `370000000`). This is what rippled
104
+ itself returns; the previous output matched no implementation.
105
+ - **`STObject#to_json` returns a Hash**, not a JSON string. The string return
106
+ forced every nested value through a parse on the way out, which is what
107
+ turned native amounts into integers.
108
+ - **Malformed input raises.** `STObject#to_json` and `STArray#to_json` caught
109
+ every parser error and returned a partially decoded object. A codec that
110
+ silently drops fields is worse than one that fails, so the rescues are gone.
111
+ - `tecHOOK_REJECTED` is no longer a known transaction result. It is a Xahau
112
+ code and never belonged in the XRP Ledger definitions.
113
+
114
+ ### Added
115
+
116
+ - **Transaction models** — `XRPL::Transaction::Payment`, `::EscrowCreate` and
117
+ 80 more, generated at load time from `TRANSACTION_FORMATS` so they cannot
118
+ drift from the definitions. Fields are written in snake_case and stored under
119
+ the ledger's own names; `#validate!` reports missing required fields before a
120
+ round trip is spent, and each type carries its flags as constants
121
+ (`Payment::TF_PARTIAL_PAYMENT`). A plain Hash still works everywhere a
122
+ transaction is accepted.
123
+ - **`BinaryCodec::Number`** — the STNumber type (12 bytes: int64 mantissa,
124
+ int32 exponent). 17 serialised fields reference it — the Vault and Lending
125
+ Protocol amounts — and no class existed, so those fields could not be
126
+ encoded at all. The gap predates this release.
127
+ - **`Issue` supports MPT** (`mpt_issuance_id`), in both directions.
128
+ - **Conformance spec** running `codec-fixtures.json`, and a definitions spec
129
+ that checks every type a serialised field uses resolves to a class.
130
+
131
+ ### Changed
132
+
133
+ - **`definitions.json` synced verbatim with XRPLF.** FIELDS 343 → 381,
134
+ TRANSACTION_TYPES 76 → 83, LEDGER_ENTRY_TYPES 31 → 32, TRANSACTION_RESULTS
135
+ 189 → 195. Adds the Sponsorship and Confidential MPT fields and transactions,
136
+ `TakerGetsMPT`, `TakerPaysMPT` and `ReferenceHolding`. Renames `UInt384`/
137
+ `UInt512` to `Hash384`/`Hash512` and `MutableFlags` to `ImmutableFlags`; the
138
+ type map keeps the old names resolving.
139
+ - `Client#autofill`, `#submit` and `#submit_and_wait` accept a transaction
140
+ model as well as a Hash.
141
+ - The gemspec packages everything under `lib/`, not only the `.rb` files. Data
142
+ files previously needed an entry of their own, and a forgotten one would have
143
+ broken the installed gem while every local run stayed green, because the
144
+ working copy has the file either way. `spec/packaging_spec.rb` checks it now.
145
+
146
+ ### Fixed
147
+
148
+ - **`Issue` read a fixed 40 bytes.** An XRP issue is 20, so the rest came out
149
+ of the following field: `Asset` and `Asset2` decoded as `nil` and
150
+ `IssuingChainDoor` as the zero account.
151
+ - **`STArray.from_parser` dropped the ArrayEndMarker** it is meant to restore.
152
+ The reconstructed bytes had no terminator, so re-parsing them ran the array
153
+ on into the fields that followed — `AMMBid` lost `Asset` and `Asset2` into
154
+ `AuthAccounts`.
155
+ - **`Currency#to_json` took no arguments** while every type is called as
156
+ `to_json(definitions, field_name)`. It raised `ArgumentError` for every
157
+ nested Currency field, which the silent rescue then swallowed — that is how
158
+ `BaseAsset` and `QuoteAsset` disappeared from `PriceDataSeries`.
159
+ - **`XChainBridge` omitted the `0x14` length prefix** before each account,
160
+ which is why all eight XChain fixtures failed to serialise.
161
+ - **A zero IOU rendered as `"-0"`** instead of `"0"`.
162
+
8
163
  ## [0.6.1] - 2026-08-12
9
164
 
10
165
  ### Added
data/README.md CHANGED
@@ -12,14 +12,19 @@ A pure-Ruby library to interact with the [XRP Ledger](https://xrpl.org) (XRPL) b
12
12
  ## Features
13
13
 
14
14
  - Key and wallet management (secp256k1 and ed25519)
15
- - Address codec and binary (transaction) codec
15
+ - Address codec and binary codec, conformant with rippled 3.4.0 and the
16
+ ripple-binary-codec reference fixtures
17
+ - Transaction and ledger entry models generated from the ledger's own
18
+ definitions
16
19
  - WebSocket client for the XRP Ledger public API
17
20
  - Testnet faucet helper to create and fund wallets
18
- - Transaction lifecycle: autofill, sign, submit, and reliable "submit and wait"
21
+ - Transaction lifecycle: autofill with per-type fees, sign, submit, and
22
+ reliable "submit and wait"
23
+ - Payment channel claims, signed and verified locally
19
24
 
20
25
  ## Requirements
21
26
 
22
- - Ruby 3.0 or later
27
+ - Ruby 3.2 or later
23
28
 
24
29
  ## Installation
25
30
 
@@ -69,6 +74,97 @@ puts result.dig('result', 'meta', 'TransactionResult') # => "tesSUCCESS"
69
74
  client.disconnect
70
75
  ```
71
76
 
77
+ ## Transactions
78
+
79
+ A transaction can always be a plain Hash, as above. The transaction classes
80
+ are an addition on top of that: they are generated from the field formats in
81
+ `definitions.json`, so they know which fields a type accepts, which of them
82
+ are required, and what its flags are called.
83
+
84
+ ```ruby
85
+ payment = XRPL::Transaction::Payment.new(
86
+ account: wallet.classic_address,
87
+ destination: receiver.classic_address,
88
+ amount: '1000000',
89
+ flags: XRPL::Transaction::Payment::TF_PARTIAL_PAYMENT
90
+ )
91
+
92
+ payment.validate! # raises before a round trip is spent
93
+ client.submit_and_wait(payment, wallet: wallet)
94
+ ```
95
+
96
+ Fields are written in snake_case and stored under the ledger's own names, so
97
+ `#to_h` produces exactly what the binary codec expects:
98
+
99
+ ```ruby
100
+ payment.to_h
101
+ # => {"TransactionType"=>"Payment", "Account"=>"r...", "Destination"=>"r...",
102
+ # "Amount"=>"1000000", "Flags"=>131072}
103
+ ```
104
+
105
+ A field the type does not define is rejected when it is set, rather than by
106
+ the server several seconds later:
107
+
108
+ ```ruby
109
+ payment.limit_amount = {} # NoMethodError
110
+ XRPL::Transaction::Payment.new(limit_amount: {})
111
+ # => XRPL::Transaction::ValidationError: Payment has no field LimitAmount
112
+ ```
113
+
114
+ `XRPL::Transaction.from(hash)` builds the matching class from a transaction
115
+ hash, which is useful for anything read back off the ledger. Note that
116
+ `validate!` follows rippled's formats: it checks what the ledger requires for
117
+ serialisation, which is not always what a transaction needs to be meaningful.
118
+
119
+ Flags can be asked about by any of their names:
120
+
121
+ ```ruby
122
+ payment.flag?(:tf_partial_payment) # => true
123
+ payment.flag_names # => ["tfPartialPayment"]
124
+ ```
125
+
126
+ ### Fees
127
+
128
+ `autofill` sets the fee the transaction type actually needs, following the
129
+ same rules as xrpl.js: an `EscrowFinish` pays for the size of its
130
+ `Fulfillment`, `AccountDelete`, `AMMCreate` and `VaultCreate` cost the owner
131
+ reserve, a `Batch` pays for its inner transactions, and multisigning adds one
132
+ base fee per signature. Ordinary fees are capped at 2 XRP; pass
133
+ `max_fee_drops:` to `Client.new` to change that. The rules are available on
134
+ their own as `XRPL::Fee.calculate`.
135
+
136
+ ## Ledger entries
137
+
138
+ The objects that make up the ledger's state - `AccountRoot`, `RippleState`,
139
+ `Offer`, `Escrow` and the rest - have models too, generated from
140
+ `LEDGER_ENTRY_FORMATS` the same way:
141
+
142
+ ```ruby
143
+ node = client.request_with_retry('ledger_entry', account_root: address).dig('result', 'node')
144
+ entry = XRPL::LedgerEntry.from(node)
145
+
146
+ entry.class # => XRPL::LedgerEntry::AccountRoot
147
+ entry.balance # => "370000000"
148
+ entry.flag?(:lsf_default_ripple) # => true
149
+ entry.flag_names # => ["lsfDefaultRipple", "lsfDisableMaster"]
150
+ entry.index # the entry's hash, as rippled reports it
151
+ ```
152
+
153
+ Every flag rippled defines is a constant on its type, e.g.
154
+ `XRPL::LedgerEntry::AccountRoot::LSF_DEPOSIT_AUTH`.
155
+
156
+ ## Payment channel claims
157
+
158
+ A claim is signed off-ledger and redeemed later with a `PaymentChannelClaim`
159
+ transaction. Amounts are in drops:
160
+
161
+ ```ruby
162
+ signature = wallet.sign_payment_channel_claim(channel_id, '1000000')
163
+
164
+ # On the receiving side, against the channel's PublicKey:
165
+ Wallet::Wallet.verify_payment_channel_claim(channel_id, '1000000', signature, public_key)
166
+ ```
167
+
72
168
  The client is silent by default. To see diagnostic output, pass a logger:
73
169
 
74
170
  ```ruby
@@ -6,7 +6,15 @@ module BinaryCodec
6
6
  transaction_sig: 0x53545800, # 'STX\0'
7
7
  transaction_multi_sig: 0x534D5400, # 'SMT\0'
8
8
  validation: 0x56414C00, # 'VAL\0'
9
- proposal: 0x50525000 # 'PRP\0'
9
+ proposal: 0x50525000, # 'PRP\0'
10
+ payment_channel_claim: 0x434C4D00, # 'CLM\0'
11
+ batch: 0x42434800, # 'BCH\0'
12
+ # Role-specific prefixes from fixCleanup3_4_0, so a signature made for
13
+ # one role cannot be replayed as another.
14
+ counterparty_sig: 0x43505400, # 'CPT\0'
15
+ counterparty_multi_sig: 0x43504D00, # 'CPM\0'
16
+ sponsor_sig: 0x53504E00, # 'SPN\0'
17
+ sponsor_multi_sig: 0x53504D00 # 'SPM\0'
10
18
  }.freeze
11
19
 
12
20
  # from here: https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/binary.ts
@@ -38,6 +46,19 @@ module BinaryCodec
38
46
  st_object.from(json).to_hex
39
47
  end
40
48
 
49
+ # The bytes a payment channel claim is signed over: the 'CLM\0' prefix,
50
+ # the channel ID and the amount in drops. Same as xrpl.js
51
+ # encodeForSigningClaim.
52
+ #
53
+ # @param channel [String] the channel ID, 64 hex characters
54
+ # @param amount [String, Integer] the amount in drops
55
+ # @return [Array<Integer>] the signing data
56
+ def signing_claim_data(channel:, amount:)
57
+ int_to_bytes(HASH_PREFIX[:payment_channel_claim], 4) +
58
+ Hash256.from(channel).to_bytes +
59
+ Uint64.from(Integer(amount)).to_bytes
60
+ end
61
+
41
62
  # Generates signing data for a transaction.
42
63
  # @param transaction [Hash] The transaction to serialize.
43
64
  # @param prefix [Integer] The prefix to add to the serialized data.