rippled
Loading...
Searching...
No Matches
NFTokenID.cpp
1#include <xrpl/basics/base_uint.h>
2#include <xrpl/json/json_value.h>
3#include <xrpl/protocol/LedgerFormats.h>
4#include <xrpl/protocol/NFTokenID.h>
5#include <xrpl/protocol/SField.h>
6#include <xrpl/protocol/STArray.h>
7#include <xrpl/protocol/STObject.h>
8#include <xrpl/protocol/STTx.h>
9#include <xrpl/protocol/TER.h>
10#include <xrpl/protocol/TxFormats.h>
11#include <xrpl/protocol/TxMeta.h>
12#include <xrpl/protocol/jss.h>
13
14#include <algorithm>
15#include <iterator>
16#include <memory>
17#include <optional>
18#include <vector>
19
20namespace xrpl {
21
22bool
23canHaveNFTokenID(std::shared_ptr<STTx const> const& serializedTx, TxMeta const& transactionMeta)
24{
25 if (!serializedTx)
26 return false;
27
28 TxType const tt = serializedTx->getTxnType();
29 if (tt != ttNFTOKEN_MINT && tt != ttNFTOKEN_ACCEPT_OFFER && tt != ttNFTOKEN_CANCEL_OFFER)
30 return false;
31
32 // if the transaction failed nothing could have been delivered.
33 if (!isTesSuccess(transactionMeta.getResultTER()))
34 return false;
35
36 return true;
37}
38
40getNFTokenIDFromPage(TxMeta const& transactionMeta)
41{
42 // The metadata does not make it obvious which NFT was added. To figure
43 // that out we gather up all of the previous NFT IDs and all of the final
44 // NFT IDs and compare them to find what changed.
46 std::vector<uint256> finalIDs;
47
48 for (STObject const& node : transactionMeta.getNodes())
49 {
50 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_PAGE)
51 continue;
52
53 SField const& fName = node.getFName();
54 if (fName == sfCreatedNode)
55 {
56 STArray const& toAddPrevNFTs =
57 node.peekAtField(sfNewFields).downcast<STObject>().getFieldArray(sfNFTokens);
59 toAddPrevNFTs.begin(),
60 toAddPrevNFTs.end(),
61 std::back_inserter(finalIDs),
62 [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); });
63 }
64 else if (fName == sfModifiedNode)
65 {
66 // When a mint results in splitting an existing page,
67 // it results in a created page and a modified node. Sometimes,
68 // the created node needs to be linked to a third page, resulting
69 // in modifying that third page's PreviousPageMin or NextPageMin
70 // field changing, but no NFTs within that page changing. In this
71 // case, there will be no previous NFTs and we need to skip.
72 // However, there will always be NFTs listed in the final fields,
73 // as rippled outputs all fields in final fields even if they were
74 // not changed.
75 STObject const& previousFields =
76 node.peekAtField(sfPreviousFields).downcast<STObject>();
77 if (!previousFields.isFieldPresent(sfNFTokens))
78 continue;
79
80 STArray const& toAddPrevNFTs = previousFields.getFieldArray(sfNFTokens);
82 toAddPrevNFTs.begin(),
83 toAddPrevNFTs.end(),
84 std::back_inserter(prevIDs),
85 [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); });
86
87 STArray const& toAddFinalNFTs =
88 node.peekAtField(sfFinalFields).downcast<STObject>().getFieldArray(sfNFTokens);
90 toAddFinalNFTs.begin(),
91 toAddFinalNFTs.end(),
92 std::back_inserter(finalIDs),
93 [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); });
94 }
95 }
96
97 // We expect NFTs to be added one at a time. So finalIDs should be one
98 // longer than prevIDs. If that's not the case something is messed up.
99 if (finalIDs.size() != prevIDs.size() + 1)
100 return std::nullopt;
101
102 // Find the first NFT ID that doesn't match. We're looking for an
103 // added NFT, so the one we want will be the mismatch in finalIDs.
104 auto const diff =
105 std::mismatch(finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end());
106
107 // There should always be a difference so the returned finalIDs
108 // iterator should never be end(). But better safe than sorry.
109 if (diff.first == finalIDs.end())
110 return std::nullopt;
111
112 return *diff.first;
113}
114
117{
118 std::vector<uint256> tokenIDResult;
119 for (STObject const& node : transactionMeta.getNodes())
120 {
121 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_OFFER ||
122 node.getFName() != sfDeletedNode)
123 continue;
124
125 auto const& toAddNFT =
126 node.peekAtField(sfFinalFields).downcast<STObject>().getFieldH256(sfNFTokenID);
127 tokenIDResult.push_back(toAddNFT);
128 }
129
130 // Deduplicate the NFT IDs because multiple offers could affect the same NFT
131 // and hence we would get duplicate NFT IDs
132 sort(tokenIDResult.begin(), tokenIDResult.end());
133 tokenIDResult.erase(unique(tokenIDResult.begin(), tokenIDResult.end()), tokenIDResult.end());
134 return tokenIDResult;
135}
136
137void
139 Json::Value& response,
140 std::shared_ptr<STTx const> const& transaction,
141 TxMeta const& transactionMeta)
142{
143 if (!canHaveNFTokenID(transaction, transactionMeta))
144 return;
145
146 // We extract the NFTokenID from metadata by comparing affected nodes
147 if (auto const type = transaction->getTxnType(); type == ttNFTOKEN_MINT)
148 {
149 std::optional<uint256> result = getNFTokenIDFromPage(transactionMeta);
150 if (result.has_value())
151 response[jss::nftoken_id] = to_string(result.value());
152 }
153 else if (type == ttNFTOKEN_ACCEPT_OFFER)
154 {
155 std::vector<uint256> result = getNFTokenIDFromDeletedOffer(transactionMeta);
156
157 if (!result.empty())
158 response[jss::nftoken_id] = to_string(result.front());
159 }
160 else if (type == ttNFTOKEN_CANCEL_OFFER)
161 {
162 std::vector<uint256> const result = getNFTokenIDFromDeletedOffer(transactionMeta);
163
164 response[jss::nftoken_ids] = Json::Value(Json::arrayValue);
165 for (auto const& nftID : result)
166 response[jss::nftoken_ids].append(to_string(nftID));
167 }
168}
169
170} // namespace xrpl
T back_inserter(T... args)
T begin(T... args)
Represents a JSON value.
Definition json_value.h:130
Identifies fields.
Definition SField.h:126
iterator begin()
Definition STArray.h:201
iterator end()
Definition STArray.h:207
D & downcast()
Definition STBase.h:193
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:680
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:456
STBase const & peekAtField(SField const &field) const
Definition STObject.cpp:401
TER getResultTER() const
Definition TxMeta.h:37
STArray & getNodes()
Definition TxMeta.h:69
T empty(T... args)
T end(T... args)
T erase(T... args)
T front(T... args)
T is_same_v
T mismatch(T... args)
@ arrayValue
array value (ordered list)
Definition json_value.h:25
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::vector< uint256 > getNFTokenIDFromDeletedOffer(TxMeta const &transactionMeta)
TxType
Transaction type identifiers.
Definition TxFormats.h:39
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
bool canHaveNFTokenID(std::shared_ptr< STTx const > const &serializedTx, TxMeta const &transactionMeta)
Add a nftoken_ids field to the meta output parameter.
Definition NFTokenID.cpp:23
void insertNFTokenID(Json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
std::optional< uint256 > getNFTokenIDFromPage(TxMeta const &transactionMeta)
Definition NFTokenID.cpp:40
T has_value(T... args)
T push_back(T... args)
T size(T... args)
T transform(T... args)
T value(T... args)