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 (transactionMeta.getResultTER() != tesSUCCESS)
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 = node.peekAtField(sfNewFields).downcast<STObject>().getFieldArray(sfNFTokens);
58 toAddPrevNFTs.begin(), toAddPrevNFTs.end(), std::back_inserter(finalIDs), [](STObject const& nft) {
59 return nft.getFieldH256(sfNFTokenID);
60 });
61 }
62 else if (fName == sfModifiedNode)
63 {
64 // When a mint results in splitting an existing page,
65 // it results in a created page and a modified node. Sometimes,
66 // the created node needs to be linked to a third page, resulting
67 // in modifying that third page's PreviousPageMin or NextPageMin
68 // field changing, but no NFTs within that page changing. In this
69 // case, there will be no previous NFTs and we need to skip.
70 // However, there will always be NFTs listed in the final fields,
71 // as rippled outputs all fields in final fields even if they were
72 // not changed.
73 STObject const& previousFields = node.peekAtField(sfPreviousFields).downcast<STObject>();
74 if (!previousFields.isFieldPresent(sfNFTokens))
75 continue;
76
77 STArray const& toAddPrevNFTs = previousFields.getFieldArray(sfNFTokens);
79 toAddPrevNFTs.begin(), toAddPrevNFTs.end(), std::back_inserter(prevIDs), [](STObject const& nft) {
80 return nft.getFieldH256(sfNFTokenID);
81 });
82
83 STArray const& toAddFinalNFTs =
84 node.peekAtField(sfFinalFields).downcast<STObject>().getFieldArray(sfNFTokens);
86 toAddFinalNFTs.begin(), toAddFinalNFTs.end(), std::back_inserter(finalIDs), [](STObject const& nft) {
87 return nft.getFieldH256(sfNFTokenID);
88 });
89 }
90 }
91
92 // We expect NFTs to be added one at a time. So finalIDs should be one
93 // longer than prevIDs. If that's not the case something is messed up.
94 if (finalIDs.size() != prevIDs.size() + 1)
95 return std::nullopt;
96
97 // Find the first NFT ID that doesn't match. We're looking for an
98 // added NFT, so the one we want will be the mismatch in finalIDs.
99 auto const diff = std::mismatch(finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end());
100
101 // There should always be a difference so the returned finalIDs
102 // iterator should never be end(). But better safe than sorry.
103 if (diff.first == finalIDs.end())
104 return std::nullopt;
105
106 return *diff.first;
107}
108
111{
112 std::vector<uint256> tokenIDResult;
113 for (STObject const& node : transactionMeta.getNodes())
114 {
115 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_OFFER || node.getFName() != sfDeletedNode)
116 continue;
117
118 auto const& toAddNFT = node.peekAtField(sfFinalFields).downcast<STObject>().getFieldH256(sfNFTokenID);
119 tokenIDResult.push_back(toAddNFT);
120 }
121
122 // Deduplicate the NFT IDs because multiple offers could affect the same NFT
123 // and hence we would get duplicate NFT IDs
124 sort(tokenIDResult.begin(), tokenIDResult.end());
125 tokenIDResult.erase(unique(tokenIDResult.begin(), tokenIDResult.end()), tokenIDResult.end());
126 return tokenIDResult;
127}
128
129void
130insertNFTokenID(Json::Value& response, std::shared_ptr<STTx const> const& transaction, TxMeta const& transactionMeta)
131{
132 if (!canHaveNFTokenID(transaction, transactionMeta))
133 return;
134
135 // We extract the NFTokenID from metadata by comparing affected nodes
136 if (auto const type = transaction->getTxnType(); type == ttNFTOKEN_MINT)
137 {
138 std::optional<uint256> result = getNFTokenIDFromPage(transactionMeta);
139 if (result.has_value())
140 response[jss::nftoken_id] = to_string(result.value());
141 }
142 else if (type == ttNFTOKEN_ACCEPT_OFFER)
143 {
144 std::vector<uint256> result = getNFTokenIDFromDeletedOffer(transactionMeta);
145
146 if (result.size() > 0)
147 response[jss::nftoken_id] = to_string(result.front());
148 }
149 else if (type == ttNFTOKEN_CANCEL_OFFER)
150 {
151 std::vector<uint256> result = getNFTokenIDFromDeletedOffer(transactionMeta);
152
153 response[jss::nftoken_ids] = Json::Value(Json::arrayValue);
154 for (auto const& nftID : result)
155 response[jss::nftoken_ids].append(to_string(nftID));
156 }
157}
158
159} // 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:199
iterator end()
Definition STArray.h:205
D & downcast()
Definition STBase.h:193
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:663
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:439
STBase const & peekAtField(SField const &field) const
Definition STObject.cpp:384
TER getResultTER() const
Definition TxMeta.h:37
STArray & getNodes()
Definition TxMeta.h:69
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:37
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
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)
std::optional< uint256 > getNFTokenIDFromPage(TxMeta const &transactionMeta)
Definition NFTokenID.cpp:40
@ tesSUCCESS
Definition TER.h:225
T has_value(T... args)
T push_back(T... args)
T size(T... args)
T transform(T... args)
T value(T... args)