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