rippled
Loading...
Searching...
No Matches
NFTokenAcceptOffer.cpp
1#include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
2#include <xrpld/app/tx/detail/NFTokenUtils.h>
3
4#include <xrpl/ledger/View.h>
5#include <xrpl/protocol/Feature.h>
6#include <xrpl/protocol/Rate.h>
7#include <xrpl/protocol/TxFlags.h>
8
9namespace xrpl {
10
16
19{
20 auto const bo = ctx.tx[~sfNFTokenBuyOffer];
21 auto const so = ctx.tx[~sfNFTokenSellOffer];
22
23 // At least one of these MUST be specified
24 if (!bo && !so)
25 return temMALFORMED;
26
27 // The `BrokerFee` field must not be present in direct mode but may be
28 // present and greater than zero in brokered mode.
29 if (auto const bf = ctx.tx[~sfNFTokenBrokerFee])
30 {
31 if (!bo || !so)
32 return temMALFORMED;
33
34 if (*bf <= beast::zero)
35 return temMALFORMED;
36 }
37
38 return tesSUCCESS;
39}
40
41TER
43{
44 auto const checkOffer = [&ctx](std::optional<uint256> id) -> std::pair<std::shared_ptr<const SLE>, TER> {
45 if (id)
46 {
47 if (id->isZero())
48 return {nullptr, tecOBJECT_NOT_FOUND};
49
50 auto offerSLE = ctx.view.read(keylet::nftoffer(*id));
51
52 if (!offerSLE)
53 return {nullptr, tecOBJECT_NOT_FOUND};
54
55 if (hasExpired(ctx.view, (*offerSLE)[~sfExpiration]))
56 {
57 // Before fixExpiredNFTokenOfferRemoval amendment, expired
58 // offers caused tecEXPIRED in preclaim, leaving them on ledger
59 // forever. After the amendment, we allow expired offers to
60 // reach doApply() where they get deleted and tecEXPIRED is
61 // returned.
62 if (!ctx.view.rules().enabled(fixExpiredNFTokenOfferRemoval))
63 return {nullptr, tecEXPIRED};
64 // Amendment enabled: return the expired offer to be handled in
65 // doApply
66 }
67
68 if ((*offerSLE)[sfAmount].negative())
69 return {nullptr, temBAD_OFFER};
70
71 return {std::move(offerSLE), tesSUCCESS};
72 }
73 return {nullptr, tesSUCCESS};
74 };
75
76 auto const [bo, err1] = checkOffer(ctx.tx[~sfNFTokenBuyOffer]);
77 if (!isTesSuccess(err1))
78 return err1;
79 auto const [so, err2] = checkOffer(ctx.tx[~sfNFTokenSellOffer]);
80 if (!isTesSuccess(err2))
81 return err2;
82
83 if (bo && so)
84 {
85 // Brokered mode:
86 // The two offers being brokered must be for the same token:
87 if ((*bo)[sfNFTokenID] != (*so)[sfNFTokenID])
89
90 // The two offers being brokered must be for the same asset:
91 if ((*bo)[sfAmount].issue() != (*so)[sfAmount].issue())
93
94 // The two offers may not form a loop. A broker may not sell the
95 // token to the current owner of the token.
96 if (((*bo)[sfOwner] == (*so)[sfOwner]))
98
99 // Ensure that the buyer is willing to pay at least as much as the
100 // seller is requesting:
101 if ((*so)[sfAmount] > (*bo)[sfAmount])
103
104 // The destination must be whoever is submitting the tx if the buyer
105 // specified it
106 if (auto const dest = bo->at(~sfDestination); dest && *dest != ctx.tx[sfAccount])
107 {
108 return tecNO_PERMISSION;
109 }
110
111 // The destination must be whoever is submitting the tx if the seller
112 // specified it
113 if (auto const dest = so->at(~sfDestination); dest && *dest != ctx.tx[sfAccount])
114 {
115 return tecNO_PERMISSION;
116 }
117
118 // The broker can specify an amount that represents their cut; if they
119 // have, ensure that the seller will get at least as much as they want
120 // to get *after* this fee is accounted for (but before the issuer's
121 // cut, if any).
122 if (auto const brokerFee = ctx.tx[~sfNFTokenBrokerFee])
123 {
124 if (brokerFee->issue() != (*bo)[sfAmount].issue())
126
127 if (brokerFee >= (*bo)[sfAmount])
129
130 if ((*so)[sfAmount] > (*bo)[sfAmount] - *brokerFee)
132
133 // Check if broker is allowed to receive the fee with these IOUs.
134 if (!brokerFee->native() && ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2))
135 {
136 auto res =
137 nft::checkTrustlineAuthorized(ctx.view, ctx.tx[sfAccount], ctx.j, brokerFee->asset().get<Issue>());
138 if (res != tesSUCCESS)
139 return res;
140
141 res =
142 nft::checkTrustlineDeepFrozen(ctx.view, ctx.tx[sfAccount], ctx.j, brokerFee->asset().get<Issue>());
143 if (res != tesSUCCESS)
144 return res;
145 }
146 }
147 }
148
149 if (bo)
150 {
151 if (((*bo)[sfFlags] & lsfSellNFToken) == lsfSellNFToken)
153
154 // An account can't accept an offer it placed:
155 if ((*bo)[sfOwner] == ctx.tx[sfAccount])
157
158 // If not in bridged mode, the account must own the token:
159 if (!so && !nft::findToken(ctx.view, ctx.tx[sfAccount], (*bo)[sfNFTokenID]))
160 return tecNO_PERMISSION;
161
162 // If not in bridged mode...
163 if (!so)
164 {
165 // If the offer has a Destination field, the acceptor must be the
166 // Destination.
167 if (auto const dest = bo->at(~sfDestination); dest.has_value() && *dest != ctx.tx[sfAccount])
168 return tecNO_PERMISSION;
169 }
170
171 // The account offering to buy must have funds:
172 //
173 // After this amendment, we allow an IOU issuer to buy an NFT with their
174 // own currency
175 auto const needed = bo->at(sfAmount);
176
177 if (accountFunds(ctx.view, (*bo)[sfOwner], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
179
180 // Check that the account accepting the buy offer (he's selling the NFT)
181 // is allowed to receive IOUs. Also check that this offer's creator is
182 // authorized. But we need to exclude the case when the transaction is
183 // created by the broker.
184 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2) && !needed.native())
185 {
186 auto res = nft::checkTrustlineAuthorized(ctx.view, bo->at(sfOwner), ctx.j, needed.asset().get<Issue>());
187 if (res != tesSUCCESS)
188 return res;
189
190 if (!so)
191 {
192 res = nft::checkTrustlineAuthorized(ctx.view, ctx.tx[sfAccount], ctx.j, needed.asset().get<Issue>());
193 if (res != tesSUCCESS)
194 return res;
195
196 res = nft::checkTrustlineDeepFrozen(ctx.view, ctx.tx[sfAccount], ctx.j, needed.asset().get<Issue>());
197 if (res != tesSUCCESS)
198 return res;
199 }
200 }
201 }
202
203 if (so)
204 {
205 if (((*so)[sfFlags] & lsfSellNFToken) != lsfSellNFToken)
207
208 // An account can't accept an offer it placed:
209 if ((*so)[sfOwner] == ctx.tx[sfAccount])
211
212 // The seller must own the token.
213 if (!nft::findToken(ctx.view, (*so)[sfOwner], (*so)[sfNFTokenID]))
214 return tecNO_PERMISSION;
215
216 // If not in bridged mode...
217 if (!bo)
218 {
219 // If the offer has a Destination field, the acceptor must be the
220 // Destination.
221 if (auto const dest = so->at(~sfDestination); dest.has_value() && *dest != ctx.tx[sfAccount])
222 return tecNO_PERMISSION;
223 }
224
225 // The account offering to buy must have funds:
226 auto const needed = so->at(sfAmount);
227 if (!bo)
228 {
229 // After this amendment, we allow buyers to buy with their own
230 // issued currency.
231 //
232 // In the case of brokered mode, this check is essentially
233 // redundant, since we have already confirmed that buy offer is >
234 // than the sell offer, and that the buyer can cover the buy
235 // offer.
236 //
237 // We also _must not_ check the tx submitter in brokered
238 // mode, because then we are confirming that the broker can
239 // cover what the buyer will pay, which doesn't make sense, causes
240 // an unnecessary tec, and is also resolved with this amendment.
241 if (accountFunds(ctx.view, ctx.tx[sfAccount], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
243 }
244
245 // Make sure that we are allowed to hold what the taker will pay us.
246 if (!needed.native())
247 {
248 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2))
249 {
250 auto res = nft::checkTrustlineAuthorized(ctx.view, (*so)[sfOwner], ctx.j, needed.asset().get<Issue>());
251 if (res != tesSUCCESS)
252 return res;
253
254 if (!bo)
255 {
256 res =
257 nft::checkTrustlineAuthorized(ctx.view, ctx.tx[sfAccount], ctx.j, needed.asset().get<Issue>());
258 if (res != tesSUCCESS)
259 return res;
260 }
261 }
262
263 auto const res =
264 nft::checkTrustlineDeepFrozen(ctx.view, (*so)[sfOwner], ctx.j, needed.asset().get<Issue>());
265 if (res != tesSUCCESS)
266 return res;
267 }
268 }
269
270 // Additional checks are required in case a minter set a transfer fee for
271 // this nftoken
272 auto const& offer = bo ? bo : so;
273 if (!offer)
274 // Purely defensive, should be caught in preflight.
275 return tecINTERNAL; // LCOV_EXCL_LINE
276
277 auto const& tokenID = offer->at(sfNFTokenID);
278 auto const& amount = offer->at(sfAmount);
279 auto const nftMinter = nft::getIssuer(tokenID);
280
281 if (nft::getTransferFee(tokenID) != 0 && !amount.native())
282 {
283 // Fix a bug where the transfer of an NFToken with a transfer fee could
284 // give the NFToken issuer an undesired trust line.
285 // Issuer doesn't need a trust line to accept their own currency.
286 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustline) &&
287 (nft::getFlags(tokenID) & nft::flagCreateTrustLines) == 0 && nftMinter != amount.getIssuer() &&
288 !ctx.view.read(keylet::line(nftMinter, amount.issue())))
289 return tecNO_LINE;
290
291 // Check that the issuer is allowed to receive IOUs.
292 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2))
293 {
294 auto res = nft::checkTrustlineAuthorized(ctx.view, nftMinter, ctx.j, amount.asset().get<Issue>());
295 if (res != tesSUCCESS)
296 return res;
297
298 res = nft::checkTrustlineDeepFrozen(ctx.view, nftMinter, ctx.j, amount.asset().get<Issue>());
299 if (res != tesSUCCESS)
300 return res;
301 }
302 }
303
304 return tesSUCCESS;
305}
306
307TER
308NFTokenAcceptOffer::pay(AccountID const& from, AccountID const& to, STAmount const& amount)
309{
310 // This should never happen, but it's easy and quick to check.
311 if (amount < beast::zero)
312 return tecINTERNAL; // LCOV_EXCL_LINE
313
314 auto const result = accountSend(view(), from, to, amount, j_);
315
316 // If any payment causes a non-IOU-issuer to have a negative balance,
317 // or an IOU-issuer to have a positive balance in their own currency,
318 // we know that something went wrong. This was originally found in the
319 // context of IOU transfer fees. Since there are several payouts in this tx,
320 // just confirm that the end state is OK.
321 if (result != tesSUCCESS)
322 return result;
323 if (accountFunds(view(), from, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
325 if (accountFunds(view(), to, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
327 return tesSUCCESS;
328}
329
330TER
331NFTokenAcceptOffer::transferNFToken(AccountID const& buyer, AccountID const& seller, uint256 const& nftokenID)
332{
333 auto tokenAndPage = nft::findTokenAndPage(view(), seller, nftokenID);
334
335 if (!tokenAndPage)
336 return tecINTERNAL; // LCOV_EXCL_LINE
337
338 if (auto const ret = nft::removeToken(view(), seller, nftokenID, std::move(tokenAndPage->page)); !isTesSuccess(ret))
339 return ret;
340
341 auto const sleBuyer = view().read(keylet::account(buyer));
342 if (!sleBuyer)
343 return tecINTERNAL; // LCOV_EXCL_LINE
344
345 std::uint32_t const buyerOwnerCountBefore = sleBuyer->getFieldU32(sfOwnerCount);
346
347 auto const insertRet = nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
348
349 // if fixNFTokenReserve is enabled, check if the buyer has sufficient
350 // reserve to own a new object, if their OwnerCount changed.
351 //
352 // There was an issue where the buyer accepts a sell offer, the ledger
353 // didn't check if the buyer has enough reserve, meaning that buyer can get
354 // NFTs free of reserve.
355 if (view().rules().enabled(fixNFTokenReserve))
356 {
357 // To check if there is sufficient reserve, we cannot use mPriorBalance
358 // because NFT is sold for a price. So we must use the balance after
359 // the deduction of the potential offer price. A small caveat here is
360 // that the balance has already deducted the transaction fee, meaning
361 // that the reserve requirement is a few drops higher.
362 auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance);
363
364 auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount);
365 if (buyerOwnerCountAfter > buyerOwnerCountBefore)
366 {
367 if (auto const reserve = view().fees().accountReserve(buyerOwnerCountAfter); buyerBalance < reserve)
369 }
370 }
371
372 return insertRet;
373}
374
375TER
377{
378 bool const isSell = offer->isFlag(lsfSellNFToken);
379 AccountID const owner = (*offer)[sfOwner];
380 AccountID const& seller = isSell ? owner : account_;
381 AccountID const& buyer = isSell ? account_ : owner;
382
383 auto const nftokenID = (*offer)[sfNFTokenID];
384
385 if (auto amount = offer->getFieldAmount(sfAmount); amount != beast::zero)
386 {
387 // Calculate the issuer's cut from this sale, if any:
388 if (auto const fee = nft::getTransferFee(nftokenID); fee != 0)
389 {
390 auto const cut = multiply(amount, nft::transferFeeAsRate(fee));
391
392 if (auto const issuer = nft::getIssuer(nftokenID);
393 cut != beast::zero && seller != issuer && buyer != issuer)
394 {
395 if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
396 return r;
397 amount -= cut;
398 }
399 }
400
401 // Send the remaining funds to the seller of the NFT
402 if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
403 return r;
404 }
405
406 // Now transfer the NFT:
407 return transferNFToken(buyer, seller, nftokenID);
408}
409
410TER
412{
413 auto const loadToken = [this](std::optional<uint256> const& id) {
415 if (id)
416 sle = view().peek(keylet::nftoffer(*id));
417 return sle;
418 };
419
420 auto bo = loadToken(ctx_.tx[~sfNFTokenBuyOffer]);
421 auto so = loadToken(ctx_.tx[~sfNFTokenSellOffer]);
422
423 // With fixExpiredNFTokenOfferRemoval amendment, check for expired offers
424 // and delete them, returning tecEXPIRED. This ensures expired offers
425 // are properly cleaned up from the ledger.
426 if (view().rules().enabled(fixExpiredNFTokenOfferRemoval))
427 {
428 bool foundExpired = false;
429
430 auto const deleteOfferIfExpired = [this, &foundExpired](std::shared_ptr<SLE> const& offer) -> TER {
431 if (offer && hasExpired(view(), (*offer)[~sfExpiration]))
432 {
433 JLOG(j_.trace()) << "Offer is expired, deleting: " << offer->key();
434 if (!nft::deleteTokenOffer(view(), offer))
435 {
436 // LCOV_EXCL_START
437 JLOG(j_.fatal()) << "Unable to delete expired offer '" << offer->key() << "': ignoring";
438 return tecINTERNAL;
439 // LCOV_EXCL_STOP
440 }
441 JLOG(j_.trace()) << "Deleted offer " << offer->key();
442 foundExpired = true;
443 }
444 return tesSUCCESS;
445 };
446
447 if (auto const r = deleteOfferIfExpired(bo); !isTesSuccess(r))
448 return r;
449 if (auto const r = deleteOfferIfExpired(so); !isTesSuccess(r))
450 return r;
451
452 if (foundExpired)
453 return tecEXPIRED;
454 }
455
456 if (bo && !nft::deleteTokenOffer(view(), bo))
457 {
458 // LCOV_EXCL_START
459 JLOG(j_.fatal()) << "Unable to delete buy offer '" << to_string(bo->key()) << "': ignoring";
460 return tecINTERNAL;
461 // LCOV_EXCL_STOP
462 }
463
464 if (so && !nft::deleteTokenOffer(view(), so))
465 {
466 // LCOV_EXCL_START
467 JLOG(j_.fatal()) << "Unable to delete sell offer '" << to_string(so->key()) << "': ignoring";
468 return tecINTERNAL;
469 // LCOV_EXCL_STOP
470 }
471
472 // Bridging two different offers
473 if (bo && so)
474 {
475 AccountID const buyer = (*bo)[sfOwner];
476 AccountID const seller = (*so)[sfOwner];
477
478 auto const nftokenID = (*so)[sfNFTokenID];
479
480 // The amount is what the buyer of the NFT pays:
481 STAmount amount = (*bo)[sfAmount];
482
483 // Three different folks may be paid. The order of operations is
484 // important.
485 //
486 // o The broker is paid the cut they requested.
487 // o The issuer's cut is calculated from what remains after the
488 // broker is paid. The issuer can take up to 50% of the remainder.
489 // o Finally, the seller gets whatever is left.
490 //
491 // It is important that the issuer's cut be calculated after the
492 // broker's portion is already removed. Calculating the issuer's
493 // cut before the broker's cut is removed can result in more money
494 // being paid out than the seller authorized. That would be bad!
495
496 // Send the broker the amount they requested.
497 if (auto const cut = ctx_.tx[~sfNFTokenBrokerFee]; cut && cut.value() != beast::zero)
498 {
499 if (auto const r = pay(buyer, account_, cut.value()); !isTesSuccess(r))
500 return r;
501
502 amount -= cut.value();
503 }
504
505 // Calculate the issuer's cut, if any.
506 if (auto const fee = nft::getTransferFee(nftokenID); amount != beast::zero && fee != 0)
507 {
508 auto cut = multiply(amount, nft::transferFeeAsRate(fee));
509
510 if (auto const issuer = nft::getIssuer(nftokenID); seller != issuer && buyer != issuer)
511 {
512 if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
513 return r;
514
515 amount -= cut;
516 }
517 }
518
519 // And send whatever remains to the seller.
520 if (amount > beast::zero)
521 {
522 if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
523 return r;
524 }
525
526 // Now transfer the NFT:
527 return transferNFToken(buyer, seller, nftokenID);
528 }
529
530 if (bo)
531 return acceptOffer(bo);
532
533 if (so)
534 return acceptOffer(so);
535
536 return tecINTERNAL; // LCOV_EXCL_LINE
537}
538
539} // namespace xrpl
Stream fatal() const
Definition Journal.h:324
Stream trace() const
Severity stream access functions.
Definition Journal.h:294
STTx const & tx
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
A currency issued by an account.
Definition Issue.h:13
TER acceptOffer(std::shared_ptr< SLE > const &offer)
TER transferNFToken(AccountID const &buyer, AccountID const &seller, uint256 const &nfTokenID)
TER pay(AccountID const &from, AccountID const &to, STAmount const &amount)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:118
AccountID const account_
Definition Transactor.h:112
beast::Journal const j_
Definition Transactor.h:110
ApplyView & view()
Definition Transactor.h:128
ApplyContext & ctx_
Definition Transactor.h:108
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:375
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:214
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
constexpr std::uint16_t const flagCreateTrustLines
Definition nft.h:35
std::optional< STObject > findToken(ReadView const &view, AccountID const &owner, uint256 const &nftokenID)
Finds the specified token in the owner's token directory.
TER removeToken(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Remove the token from the owner's token directory.
TER checkTrustlineDeepFrozen(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
TER checkTrustlineAuthorized(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
AccountID getIssuer(uint256 const &id)
Definition nft.h:100
std::uint16_t getTransferFee(uint256 const &id)
Definition nft.h:48
std::optional< TokenAndPage > findTokenAndPage(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Rate transferFeeAsRate(std::uint16_t fee)
Given a transfer fee (in basis points) convert it to a transfer rate.
Definition Rate2.cpp:26
std::uint16_t getFlags(uint256 const &id)
Definition nft.h:40
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ fhZERO_IF_FROZEN
Definition View.h:58
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:129
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2446
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:515
constexpr std::uint32_t const tfNFTokenAcceptOfferMask
Definition TxFlags.h:218
@ temMALFORMED
Definition TER.h:67
@ temBAD_OFFER
Definition TER.h:75
bool isTesSuccess(TER x) noexcept
Definition TER.h:649
@ tecCANT_ACCEPT_OWN_NFTOKEN_OFFER
Definition TER.h:305
@ tecOBJECT_NOT_FOUND
Definition TER.h:307
@ tecINTERNAL
Definition TER.h:291
@ tecNFTOKEN_BUY_SELL_MISMATCH
Definition TER.h:303
@ tecINSUFFICIENT_FUNDS
Definition TER.h:306
@ tecEXPIRED
Definition TER.h:295
@ tecNO_LINE
Definition TER.h:282
@ tecNFTOKEN_OFFER_TYPE_MISMATCH
Definition TER.h:304
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecNO_PERMISSION
Definition TER.h:286
@ tecINSUFFICIENT_PAYMENT
Definition TER.h:308
@ lsfSellNFToken
@ tesSUCCESS
Definition TER.h:225
uint256 key
Definition Keylet.h:20
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:53
ReadView const & view
Definition Transactor.h:56
beast::Journal const j
Definition Transactor.h:61
State information when preflighting a tx.
Definition Transactor.h:15