Merge remote-tracking branch 'Transia-RnD-rippled/dangell7/subscriptions' into develop

# Conflicts:
#	include/xrpl/protocol/Indexes.h
#	include/xrpl/protocol/LedgerFormats.h
#	include/xrpl/protocol/TxFlags.h
#	include/xrpl/protocol/jss.h
#	include/xrpl/tx/invariants/InvariantCheck.h
#	src/libxrpl/protocol/Indexes.cpp
This commit is contained in:
Denis Angell
2026-09-13 19:37:19 -04:00
32 changed files with 8542 additions and 1 deletions

View File

@@ -0,0 +1,429 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
namespace xrpl {
template <ValidIssueType T>
TER
canTransferTokenHelper(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal const& j);
template <>
inline TER
canTransferTokenHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal const& j)
{
AccountID issuer = amount.getIssuer();
if (issuer == account)
{
JLOG(j.trace()) << "canTransferTokenHelper: Issuer is the same as the account.";
return tesSUCCESS;
}
// If the issuer does not exist, return tecNO_ISSUER
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
{
JLOG(j.trace()) << "canTransferTokenHelper: Issuer does not exist.";
return tecNO_ISSUER;
}
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState =
view.read(keylet::trustLine(account, issuer, amount.get<Issue>().currency));
if (!sleRippleState)
{
JLOG(j.trace()) << "canTransferTokenHelper: Trust line does not exist.";
return tecNO_LINE;
}
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
{
JLOG(j.trace()) << "canTransferTokenHelper: Invalid trust line state.";
return tecNO_PERMISSION;
}
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
{
JLOG(j.trace()) << "canTransferTokenHelper: Invalid trust line state.";
return tecNO_PERMISSION;
}
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); ter != tesSUCCESS)
{
JLOG(j.trace()) << "canTransferTokenHelper: Account is not authorized";
return ter;
}
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), dest); ter != tesSUCCESS)
{
JLOG(j.trace()) << "canTransferTokenHelper: Destination is not authorized.";
return ter;
}
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(view, account, amount.get<Issue>()) ||
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.get<Issue>().account))
{
JLOG(j.trace()) << "canTransferTokenHelper: Account is frozen.";
return tecFROZEN;
}
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(view, dest, amount.get<Issue>()) ||
isDeepFrozen(view, dest, amount.get<Issue>().currency, amount.get<Issue>().account))
{
JLOG(j.trace()) << "canTransferTokenHelper: Destination is frozen.";
return tecFROZEN;
}
STAmount const spendableAmount = accountHolds(
view, account, amount.get<Issue>().currency, issuer, FreezeHandling::IgnoreFreeze, j);
// If the balance is less than or equal to 0, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
{
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
"than or equal to 0.";
return tecINSUFFICIENT_FUNDS;
}
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
{
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
"than the amount.";
return tecINSUFFICIENT_FUNDS;
}
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
inline TER
canTransferTokenHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal const& j)
{
AccountID issuer = amount.getIssuer();
if (issuer == account)
{
JLOG(j.trace()) << "canTransferTokenHelper: Issuer is the same as the account.";
return tesSUCCESS;
}
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
{
JLOG(j.trace()) << "canTransferTokenHelper: MPT issuance does not exist.";
return tecOBJECT_NOT_FOUND;
}
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
{
JLOG(j.trace()) << "canTransferTokenHelper: Issuer is not the same as "
"the issuer of the MPT.";
return tecNO_PERMISSION;
}
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
{
JLOG(j.trace()) << "canTransferTokenHelper: Account does not have the MPT.";
return tecOBJECT_NOT_FOUND;
}
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
ter != tesSUCCESS)
{
JLOG(j.trace()) << "canTransferTokenHelper: Account is not authorized.";
return ter;
}
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); ter != tesSUCCESS)
{
JLOG(j.trace()) << "canTransferTokenHelper: Destination is not authorized.";
return ter;
}
// If the issuer has locked the account, return tecLOCKED
if (isFrozen(view, account, mptIssue))
{
JLOG(j.trace()) << "canTransferTokenHelper: Account is locked.";
return tecLOCKED;
}
// If the issuer has locked the destination, return tecLOCKED
if (isFrozen(view, dest, mptIssue))
{
JLOG(j.trace()) << "canTransferTokenHelper: Destination is locked.";
return tecLOCKED;
}
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(view, mptIssue, account, dest); ter != tesSUCCESS)
{
JLOG(j.trace()) << "canTransferTokenHelper: MPT cannot be transferred.";
return ter;
}
STAmount const spendableAmount = accountHolds(
view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j);
// If the balance is less than or equal to 0, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
{
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
"than or equal to 0.";
return tecINSUFFICIENT_FUNDS;
}
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
{
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
"than the amount.";
return tecINSUFFICIENT_FUNDS;
}
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <ValidIssueType T>
TER
doTransferTokenHelper(
ApplyView& view,
SLE::ref sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal);
template <>
inline TER
doTransferTokenHelper<Issue>(
ApplyView& view,
SLE::ref sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
Keylet const trustLineKey = keylet::trustLine(receiver, amount.get<Issue>());
bool const recvLow = issuer > receiver;
// Review Note: We could remove this and just say to use batch to auth the
// token first
if (!view.exists(trustLineKey) && createAsset && issuer != receiver)
{
// Can the account cover the trust line's reserve?
if (xrpBalance < accountReserve(view, sleDest, journal, {.ownerCountDelta = 1}))
{
JLOG(journal.trace()) << "doTransferTokenHelper: Trust line does not exist. "
"Insufficent reserve to create line.";
return tecNO_LINE_INSUF_RESERVE;
}
Currency const currency = amount.get<Issue>().currency;
STAmount initialBalance(amount.get<Issue>());
initialBalance.get<Issue>().account = noAccount();
// clang-format off
if (TER const ter = trustCreate(
view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
(sleDest->getFlags() & lsfDefaultRipple) == 0,
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
SLE::pointer(), // sponsor
journal); // journal
!isTesSuccess(ter))
{
JLOG(journal.trace()) << "doTransferTokenHelper: Failed to create trust line: " << transToken(ter);
return ter;
}
// clang-format on
view.update(sleDest);
}
if (!view.exists(trustLineKey) && issuer != receiver)
return tecNO_LINE;
auto const ter =
accountSend(view, sender, receiver, amount, journal, SLE::pointer(), WaiveTransferFee::No);
if (ter != tesSUCCESS)
{
JLOG(journal.trace()) << "doTransferTokenHelper: Failed to send token: " << transToken(ter);
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
template <>
inline TER
doTransferTokenHelper<MPTIssue>(
ApplyView& view,
SLE::ref sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset &&
issuer != receiver)
{
if (xrpBalance < accountReserve(view, sleDest, journal, {.ownerCountDelta = 1}))
{
JLOG(journal.trace()) << "doTransferTokenHelper: MPT does not exist. "
"Insufficent reserve to create MPT.";
return tecINSUFFICIENT_RESERVE;
}
if (auto const ter = createMPToken(view, mptID, receiver, SLE::pointer(), 0);
!isTesSuccess(ter))
{
JLOG(journal.trace()) << "doTransferTokenHelper: Failed to create MPT: "
<< transToken(ter);
return ter;
}
// Update owner count.
increaseOwnerCount(view, sleDest, SLE::pointer(), 1, journal);
}
if (issuer != receiver && !view.exists(keylet::mptoken(issuanceKey.key, receiver)))
{
JLOG(journal.trace()) << "doTransferTokenHelper: MPT does not exist.";
return tecNO_PERMISSION;
}
auto const ter =
accountSend(view, sender, receiver, amount, journal, SLE::pointer(), WaiveTransferFee::No);
if (ter != tesSUCCESS)
{
JLOG(journal.trace()) << "doTransferTokenHelper: Failed to send MPT: " << transToken(ter);
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
// Remove a subscription from both owner directories, release the owner's
// reserve, and erase the object. Shared by SubscriptionCancel and the
// single-use claim path so the two never diverge.
inline TER
deleteSubscription(ApplyView& view, SLE::ref sleSub, beast::Journal journal)
{
AccountID const account{sleSub->getAccountID(sfAccount)};
AccountID const dstAcct{sleSub->getAccountID(sfDestination)};
std::uint64_t const ownerPage{(*sleSub)[sfOwnerNode]};
if (!view.dirRemove(keylet::ownerDir(account), ownerPage, sleSub->key(), true))
{
JLOG(journal.fatal()) << "deleteSubscription: Unable to delete from source.";
return tefBAD_LEDGER;
}
std::uint64_t const destPage{(*sleSub)[sfDestinationNode]};
if (!view.dirRemove(keylet::ownerDir(dstAcct), destPage, sleSub->key(), true))
{
JLOG(journal.fatal()) << "deleteSubscription: Unable to delete from destination.";
return tefBAD_LEDGER;
}
auto const sleSrc = view.peek(keylet::account(account));
decreaseOwnerCount(view, sleSrc, SLE::pointer(), 1, journal);
view.erase(sleSub);
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -614,6 +614,14 @@ ammBinHolding(uint256 const& ammID, AccountID const& owner, std::int32_t binID)
Keylet
ammBinHolding(uint256 const& key) noexcept;
Keylet
subscription(AccountID const& account, AccountID const& dest, std::uint32_t seq) noexcept;
inline Keylet
subscription(uint256 const& key) noexcept
{
return {ltSUBSCRIPTION, key};
}
} // namespace keylet
// Everything below is deprecated and should be removed in favor of keylets:

View File

@@ -211,7 +211,10 @@ enum LedgerEntryType : std::uint16_t {
\
LEDGER_OBJECT(Ballot, \
LSF_FLAG(lsfBallotFinalized, 0x00000001) /* True, results have been published */ \
LSF_FLAG(lsfVoterRecoverable, 0x00000002)) /* True, casts carry a voter self-recovery vector */
LSF_FLAG(lsfVoterRecoverable, 0x00000002)) /* True, casts carry a voter self-recovery vector */ \
\
LEDGER_OBJECT(Subscription, \
LSF_FLAG(lsfSingleUse, 0x00010000)) /* True, delete on first successful claim */
// clang-format on

View File

@@ -261,6 +261,10 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
\
TRANSACTION(BallotCreate, \
TF_FLAG(tfVoterRecoverable, lsfVoterRecoverable), /* casts must carry a voter self-recovery vector */ \
MASK_ADJ(0)) \
\
TRANSACTION(SubscriptionSet, /* True, delete the subscription on the first successful claim */ \
TF_FLAG(tfSingleUse, 0x00010000), \
MASK_ADJ(0))
constexpr std::uint32_t tfSendAmount = 0x00010000;

View File

@@ -159,3 +159,4 @@ XRPL_FEATURE(CouponPayments, Supported::Yes, VoteBehavior::DefaultN
XRPL_FEATURE(ConfidentialVoting, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMMCurves, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(OfferQualifiers, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Subscription, Supported::No, VoteBehavior::DefaultNo)

View File

@@ -865,6 +865,21 @@ LEDGER_ENTRY(ltAMM_BIN_HOLDING, 0x0097, AMMBinHolding, amm_bin_holding, ({
{sfFeeGrowthInsideLast1, SoeRequired},
{sfOwnerNode, SoeRequired},
}))
LEDGER_ENTRY(ltSUBSCRIPTION, 0x008A, Subscription, subscription, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfSequence, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfAccount, SoeRequired},
{sfDestination, SoeRequired},
{sfDestinationTag, SoeOptional},
{sfAmount, SoeRequired},
{sfBalance, SoeRequired},
{sfFrequency, SoeRequired},
{sfNextClaimTime, SoeRequired},
{sfExpiration, SoeOptional},
{sfDestinationNode, SoeRequired},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE

View File

@@ -561,3 +561,7 @@ TYPED_SFIELD(sfTokensOwed1, AMOUNT, 41)
TYPED_SFIELD(sfReserve0, AMOUNT, 42)
TYPED_SFIELD(sfReserve1, AMOUNT, 43)
TYPED_SFIELD(sfMinQuantity, AMOUNT, 44)
TYPED_SFIELD(sfFrequency, UINT32, 109)
TYPED_SFIELD(sfStartTime, UINT32, 110)
TYPED_SFIELD(sfNextClaimTime, UINT32, 111)
TYPED_SFIELD(sfSubscriptionID, UINT256, 49)

View File

@@ -1499,3 +1499,29 @@ TRANSACTION(ttAMM_BIN_DESTROY, 120, AMMBinDestroy,
{sfAsset2, SoeRequired, SoeMptSupported},
{sfBinID, SoeRequired},
}))
TRANSACTION(ttSUBSCRIPTION_SET, 121, SubscriptionSet,
({.delegable = Delegation::Delegable, .amendment = featureSubscription}),
({
{sfDestination, SoeOptional},
{sfAmount, SoeRequired, SoeMptSupported},
{sfFrequency, SoeOptional},
{sfStartTime, SoeOptional},
{sfExpiration, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfSubscriptionID, SoeOptional},
}))
TRANSACTION(ttSUBSCRIPTION_CANCEL, 122, SubscriptionCancel,
({.delegable = Delegation::Delegable, .amendment = featureSubscription}),
({
{sfSubscriptionID, SoeRequired},
}))
TRANSACTION(ttSUBSCRIPTION_CLAIM, 123, SubscriptionClaim,
({
.delegable = Delegation::Delegable,
.amendment = featureSubscription,
.privileges = Privilege::MayCreateMpt,
}),
({
{sfAmount, SoeRequired, SoeMptSupported},
{sfSubscriptionID, SoeRequired},
}))

View File

@@ -47,6 +47,7 @@ JSS(Destination); // in: TransactionSign; field.
JSS(EPrice); // in: AMM Deposit option
JSS(Fee); // in/out: TransactionSign; field.
JSS(Flags); // in/out: TransactionSign; field.
JSS(Frequency); // in: Subscription transactions
JSS(Holder); // field.
JSS(Invalid); //
JSS(Issuer); // in: Credential transactions
@@ -81,6 +82,7 @@ JSS(Signer); // field.
JSS(Signers); // field.
JSS(SigningPubKey); // field.
JSS(Subject); // in: Credential transactions
JSS(SubscriptionID); // in: Subscription transactions
JSS(TakerGets); // field.
JSS(TakerPays); // field.
JSS(TradingFee); // in/out: AMM trading fee

View File

@@ -0,0 +1,431 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class SubscriptionBuilder;
/**
* @brief Ledger Entry: Subscription
*
* Type: ltSUBSCRIPTION (0x008A)
* RPC Name: subscription
*
* Immutable wrapper around SLE providing type-safe field access.
* Use SubscriptionBuilder to construct new ledger entries.
*/
class Subscription : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltSUBSCRIPTION;
/**
* @brief Construct a Subscription ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Subscription(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for Subscription");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfSequence (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getSequence() const
{
return this->sle_->at(sfSequence);
}
/**
* @brief Get sfOwnerNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfAccount (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getAccount() const
{
return this->sle_->at(sfAccount);
}
/**
* @brief Get sfDestination (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getDestination() const
{
return this->sle_->at(sfDestination);
}
/**
* @brief Get sfDestinationTag (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getDestinationTag() const
{
if (hasDestinationTag())
return this->sle_->at(sfDestinationTag);
return std::nullopt;
}
/**
* @brief Check if sfDestinationTag is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationTag() const
{
return this->sle_->isFieldPresent(sfDestinationTag);
}
/**
* @brief Get sfAmount (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_AMOUNT::type::value_type
getAmount() const
{
return this->sle_->at(sfAmount);
}
/**
* @brief Get sfBalance (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_AMOUNT::type::value_type
getBalance() const
{
return this->sle_->at(sfBalance);
}
/**
* @brief Get sfFrequency (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getFrequency() const
{
return this->sle_->at(sfFrequency);
}
/**
* @brief Get sfNextClaimTime (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getNextClaimTime() const
{
return this->sle_->at(sfNextClaimTime);
}
/**
* @brief Get sfExpiration (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getExpiration() const
{
if (hasExpiration())
return this->sle_->at(sfExpiration);
return std::nullopt;
}
/**
* @brief Check if sfExpiration is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasExpiration() const
{
return this->sle_->isFieldPresent(sfExpiration);
}
/**
* @brief Get sfDestinationNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getDestinationNode() const
{
return this->sle_->at(sfDestinationNode);
}
};
/**
* @brief Builder for Subscription ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses STObject internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class SubscriptionBuilder : public LedgerEntryBuilderBase<SubscriptionBuilder>
{
public:
/**
* @brief Construct a new SubscriptionBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param sequence The sfSequence field value.
* @param ownerNode The sfOwnerNode field value.
* @param account The sfAccount field value.
* @param destination The sfDestination field value.
* @param amount The sfAmount field value.
* @param balance The sfBalance field value.
* @param frequency The sfFrequency field value.
* @param nextClaimTime The sfNextClaimTime field value.
* @param destinationNode The sfDestinationNode field value.
*/
SubscriptionBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_UINT32::type::value_type> const& sequence,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_ACCOUNT::type::value_type> const& account,std::decay_t<typename SF_ACCOUNT::type::value_type> const& destination,std::decay_t<typename SF_AMOUNT::type::value_type> const& amount,std::decay_t<typename SF_AMOUNT::type::value_type> const& balance,std::decay_t<typename SF_UINT32::type::value_type> const& frequency,std::decay_t<typename SF_UINT32::type::value_type> const& nextClaimTime,std::decay_t<typename SF_UINT64::type::value_type> const& destinationNode)
: LedgerEntryBuilderBase<SubscriptionBuilder>(ltSUBSCRIPTION)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setSequence(sequence);
setOwnerNode(ownerNode);
setAccount(account);
setDestination(destination);
setAmount(amount);
setBalance(balance);
setFrequency(frequency);
setNextClaimTime(nextClaimTime);
setDestinationNode(destinationNode);
}
/**
* @brief Construct a SubscriptionBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
SubscriptionBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltSUBSCRIPTION)
{
throw std::runtime_error("Invalid ledger entry type for Subscription");
}
object_ = *sle;
}
/**
* @brief Ledger entry-specific field setters
*/
/**
* @brief Set sfPreviousTxnID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfSequence (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setSequence(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSequence] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Set sfAccount (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfAccount] = value;
return *this;
}
/**
* @brief Set sfDestination (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfDestination] = value;
return *this;
}
/**
* @brief Set sfDestinationTag (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfDestinationTag] = value;
return *this;
}
/**
* @brief Set sfAmount (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfAmount] = value;
return *this;
}
/**
* @brief Set sfBalance (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setBalance(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfBalance] = value;
return *this;
}
/**
* @brief Set sfFrequency (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setFrequency(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfFrequency] = value;
return *this;
}
/**
* @brief Set sfNextClaimTime (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setNextClaimTime(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfNextClaimTime] = value;
return *this;
}
/**
* @brief Set sfExpiration (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfExpiration] = value;
return *this;
}
/**
* @brief Set sfDestinationNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionBuilder&
setDestinationNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfDestinationNode] = value;
return *this;
}
/**
* @brief Build and return the completed Subscription wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
Subscription
build(uint256 const& index)
{
return Subscription{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,131 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SubscriptionCancelBuilder;
/**
* @brief Transaction: SubscriptionCancel
*
* Type: ttSUBSCRIPTION_CANCEL (93)
* Delegable: Delegation::Delegable
* Amendment: featureSubscription
* Privileges: Privilege::NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SubscriptionCancelBuilder to construct new transactions.
*/
class SubscriptionCancel : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSUBSCRIPTION_CANCEL;
/**
* @brief Construct a SubscriptionCancel transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SubscriptionCancel(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SubscriptionCancel");
}
}
// Transaction-specific field getters
/**
* @brief Get sfSubscriptionID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getSubscriptionID() const
{
return this->tx_->at(sfSubscriptionID);
}
};
/**
* @brief Builder for SubscriptionCancel transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SubscriptionCancelBuilder : public TransactionBuilderBase<SubscriptionCancelBuilder>
{
public:
/**
* @brief Construct a new SubscriptionCancelBuilder with required fields.
* @param account The account initiating the transaction.
* @param subscriptionID The sfSubscriptionID field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SubscriptionCancelBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT256::type::value_type> const& subscriptionID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SubscriptionCancelBuilder>(ttSUBSCRIPTION_CANCEL, account, sequence, fee)
{
setSubscriptionID(subscriptionID);
}
/**
* @brief Construct a SubscriptionCancelBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SubscriptionCancelBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSUBSCRIPTION_CANCEL)
{
throw std::runtime_error("Invalid transaction type for SubscriptionCancelBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfSubscriptionID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionCancelBuilder&
setSubscriptionID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfSubscriptionID] = value;
return *this;
}
/**
* @brief Build and return the SubscriptionCancel wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SubscriptionCancel
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SubscriptionCancel{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,157 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SubscriptionClaimBuilder;
/**
* @brief Transaction: SubscriptionClaim
*
* Type: ttSUBSCRIPTION_CLAIM (94)
* Delegable: Delegation::Delegable
* Amendment: featureSubscription
* Privileges: Privilege::MayCreateMpt
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SubscriptionClaimBuilder to construct new transactions.
*/
class SubscriptionClaim : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSUBSCRIPTION_CLAIM;
/**
* @brief Construct a SubscriptionClaim transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SubscriptionClaim(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SubscriptionClaim");
}
}
// Transaction-specific field getters
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
SF_AMOUNT::type::value_type
getAmount() const
{
return this->tx_->at(sfAmount);
}
/**
* @brief Get sfSubscriptionID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getSubscriptionID() const
{
return this->tx_->at(sfSubscriptionID);
}
};
/**
* @brief Builder for SubscriptionClaim transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SubscriptionClaimBuilder : public TransactionBuilderBase<SubscriptionClaimBuilder>
{
public:
/**
* @brief Construct a new SubscriptionClaimBuilder with required fields.
* @param account The account initiating the transaction.
* @param amount The sfAmount field value.
* @param subscriptionID The sfSubscriptionID field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SubscriptionClaimBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::decay_t<typename SF_UINT256::type::value_type> const& subscriptionID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SubscriptionClaimBuilder>(ttSUBSCRIPTION_CLAIM, account, sequence, fee)
{
setAmount(amount);
setSubscriptionID(subscriptionID);
}
/**
* @brief Construct a SubscriptionClaimBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SubscriptionClaimBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSUBSCRIPTION_CLAIM)
{
throw std::runtime_error("Invalid transaction type for SubscriptionClaimBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
SubscriptionClaimBuilder&
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfAmount] = value;
return *this;
}
/**
* @brief Set sfSubscriptionID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SubscriptionClaimBuilder&
setSubscriptionID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfSubscriptionID] = value;
return *this;
}
/**
* @brief Build and return the SubscriptionClaim wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SubscriptionClaim
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SubscriptionClaim{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,355 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SubscriptionSetBuilder;
/**
* @brief Transaction: SubscriptionSet
*
* Type: ttSUBSCRIPTION_SET (92)
* Delegable: Delegation::Delegable
* Amendment: featureSubscription
* Privileges: Privilege::NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SubscriptionSetBuilder to construct new transactions.
*/
class SubscriptionSet : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSUBSCRIPTION_SET;
/**
* @brief Construct a SubscriptionSet transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SubscriptionSet(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SubscriptionSet");
}
}
// Transaction-specific field getters
/**
* @brief Get sfDestination (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getDestination() const
{
if (hasDestination())
{
return this->tx_->at(sfDestination);
}
return std::nullopt;
}
/**
* @brief Check if sfDestination is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestination() const
{
return this->tx_->isFieldPresent(sfDestination);
}
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
SF_AMOUNT::type::value_type
getAmount() const
{
return this->tx_->at(sfAmount);
}
/**
* @brief Get sfFrequency (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getFrequency() const
{
if (hasFrequency())
{
return this->tx_->at(sfFrequency);
}
return std::nullopt;
}
/**
* @brief Check if sfFrequency is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFrequency() const
{
return this->tx_->isFieldPresent(sfFrequency);
}
/**
* @brief Get sfStartTime (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getStartTime() const
{
if (hasStartTime())
{
return this->tx_->at(sfStartTime);
}
return std::nullopt;
}
/**
* @brief Check if sfStartTime is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasStartTime() const
{
return this->tx_->isFieldPresent(sfStartTime);
}
/**
* @brief Get sfExpiration (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getExpiration() const
{
if (hasExpiration())
{
return this->tx_->at(sfExpiration);
}
return std::nullopt;
}
/**
* @brief Check if sfExpiration is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasExpiration() const
{
return this->tx_->isFieldPresent(sfExpiration);
}
/**
* @brief Get sfDestinationTag (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getDestinationTag() const
{
if (hasDestinationTag())
{
return this->tx_->at(sfDestinationTag);
}
return std::nullopt;
}
/**
* @brief Check if sfDestinationTag is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationTag() const
{
return this->tx_->isFieldPresent(sfDestinationTag);
}
/**
* @brief Get sfSubscriptionID (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getSubscriptionID() const
{
if (hasSubscriptionID())
{
return this->tx_->at(sfSubscriptionID);
}
return std::nullopt;
}
/**
* @brief Check if sfSubscriptionID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSubscriptionID() const
{
return this->tx_->isFieldPresent(sfSubscriptionID);
}
};
/**
* @brief Builder for SubscriptionSet transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SubscriptionSetBuilder : public TransactionBuilderBase<SubscriptionSetBuilder>
{
public:
/**
* @brief Construct a new SubscriptionSetBuilder with required fields.
* @param account The account initiating the transaction.
* @param amount The sfAmount field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SubscriptionSetBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SubscriptionSetBuilder>(ttSUBSCRIPTION_SET, account, sequence, fee)
{
setAmount(amount);
}
/**
* @brief Construct a SubscriptionSetBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SubscriptionSetBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSUBSCRIPTION_SET)
{
throw std::runtime_error("Invalid transaction type for SubscriptionSetBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfDestination (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionSetBuilder&
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfDestination] = value;
return *this;
}
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
SubscriptionSetBuilder&
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfAmount] = value;
return *this;
}
/**
* @brief Set sfFrequency (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionSetBuilder&
setFrequency(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfFrequency] = value;
return *this;
}
/**
* @brief Set sfStartTime (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionSetBuilder&
setStartTime(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfStartTime] = value;
return *this;
}
/**
* @brief Set sfExpiration (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionSetBuilder&
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfExpiration] = value;
return *this;
}
/**
* @brief Set sfDestinationTag (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionSetBuilder&
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfDestinationTag] = value;
return *this;
}
/**
* @brief Set sfSubscriptionID (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SubscriptionSetBuilder&
setSubscriptionID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfSubscriptionID] = value;
return *this;
}
/**
* @brief Build and return the SubscriptionSet wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SubscriptionSet
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SubscriptionSet{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -18,6 +18,7 @@
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
#include <xrpl/tx/invariants/SubscriptionInvariant.h>
#include <xrpl/tx/invariants/TokenIssuanceInvariant.h>
#include <xrpl/tx/invariants/VaultAccrualInvariant.h>
#include <xrpl/tx/invariants/VaultInvariant.h>
@@ -507,6 +508,7 @@ using InvariantChecks = std::tuple<
ValidLoan,
ValidVault,
ValidVaultAccrual,
ValidSubscription,
ValidConfidentialMPToken,
ValidMPTBalanceChanges,
ValidAmounts,

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <memory>
#include <vector>
namespace xrpl {
/**
* @brief Invariant: a Subscription ledger entry holds a well-formed balance and
* distinct parties.
*
* Enforces XLS-78 2.1.1.7 for every Subscription entry the transaction leaves in
* the ledger: Balance is not negative, Balance and Amount are denominated in the
* same asset, and Account differs from Destination.
*/
class ValidSubscription
{
std::vector<std::shared_ptr<STLedgerEntry const>> subscriptions_;
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -0,0 +1,44 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SubscriptionCancel : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SubscriptionCancel(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -0,0 +1,44 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SubscriptionClaim : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SubscriptionClaim(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -0,0 +1,47 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SubscriptionSet : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SubscriptionSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl