xrpld
Loading...
Searching...
No Matches
NFTokenID.cpp
1#include <xrpl/protocol/NFTokenID.h>
2
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/json/json_value.h>
5#include <xrpl/protocol/LedgerFormats.h>
6#include <xrpl/protocol/SField.h>
7#include <xrpl/protocol/STArray.h>
8#include <xrpl/protocol/STObject.h>
9#include <xrpl/protocol/STTx.h>
10#include <xrpl/protocol/TER.h>
11#include <xrpl/protocol/TxFormats.h>
12#include <xrpl/protocol/TxMeta.h>
13#include <xrpl/protocol/jss.h>
14
15#include <algorithm>
16#include <iterator>
17#include <memory>
18#include <optional>
19#include <vector>
20
21namespace xrpl {
22
23bool
24canHaveNFTokenID(std::shared_ptr<STTx const> const& serializedTx, TxMeta const& transactionMeta)
25{
26 if (!serializedTx)
27 return false;
28
29 TxType const tt = serializedTx->getTxnType();
30 if (tt != ttNFTOKEN_MINT && tt != ttNFTOKEN_ACCEPT_OFFER && tt != ttNFTOKEN_CANCEL_OFFER)
31 return false;
32
33 // if the transaction failed nothing could have been delivered.
34 if (!isTesSuccess(transactionMeta.getResultTER()))
35 return false;
36
37 return true;
38}
39
41getNFTokenIDFromPage(TxMeta const& transactionMeta)
42{
43 // The metadata does not make it obvious which NFT was added. To figure
44 // that out we gather up all of the previous NFT IDs and all of the final
45 // NFT IDs and compare them to find what changed.
47 std::vector<uint256> finalIDs;
48
49 for (STObject const& node : transactionMeta.getNodes())
50 {
51 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_PAGE)
52 continue;
53
54 SField const& fName = node.getFName();
55 if (fName == sfCreatedNode)
56 {
57 STArray const& toAddPrevNFTs =
58 node.peekAtField(sfNewFields).downcast<STObject>().getFieldArray(sfNFTokens);
60 toAddPrevNFTs, std::back_inserter(finalIDs), [](STObject const& nft) {
61 return nft.getFieldH256(sfNFTokenID);
62 });
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 xrpld 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, std::back_inserter(prevIDs), [](STObject const& nft) {
83 return nft.getFieldH256(sfNFTokenID);
84 });
85
86 STArray const& toAddFinalNFTs =
87 node.peekAtField(sfFinalFields).downcast<STObject>().getFieldArray(sfNFTokens);
89 toAddFinalNFTs, std::back_inserter(finalIDs), [](STObject const& nft) {
90 return nft.getFieldH256(sfNFTokenID);
91 });
92 }
93 }
94
95 // We expect NFTs to be added one at a time. So finalIDs should be one
96 // longer than prevIDs. If that's not the case something is messed up.
97 if (finalIDs.size() != prevIDs.size() + 1)
98 return std::nullopt;
99
100 // Find the first NFT ID that doesn't match. We're looking for an
101 // added NFT, so the one we want will be the mismatch in finalIDs.
102 auto const diff = std::ranges::mismatch(finalIDs, prevIDs);
103
104 // There should always be a difference so the returned finalIDs
105 // iterator should never be end(). But better safe than sorry.
106 if (diff.in1 == finalIDs.end())
107 return std::nullopt;
108
109 return *diff.in1;
110}
111
114{
115 std::vector<uint256> tokenIDResult;
116 for (STObject const& node : transactionMeta.getNodes())
117 {
118 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_OFFER ||
119 node.getFName() != sfDeletedNode)
120 continue;
121
122 auto const& toAddNFT =
123 node.peekAtField(sfFinalFields).downcast<STObject>().getFieldH256(sfNFTokenID);
124 tokenIDResult.push_back(toAddNFT);
125 }
126
127 // Deduplicate the NFT IDs because multiple offers could affect the same NFT
128 // and hence we would get duplicate NFT IDs
129 std::ranges::sort(tokenIDResult);
130 auto const uniq = std::ranges::unique(tokenIDResult);
131 tokenIDResult.erase(uniq.begin(), uniq.end());
132 return tokenIDResult;
133}
134
135void
137 json::Value& response,
138 std::shared_ptr<STTx const> const& transaction,
139 TxMeta const& transactionMeta)
140{
141 if (!canHaveNFTokenID(transaction, transactionMeta))
142 return;
143
144 // We extract the NFTokenID from metadata by comparing affected nodes
145 if (auto const type = transaction->getTxnType(); type == ttNFTOKEN_MINT)
146 {
147 std::optional<uint256> result = getNFTokenIDFromPage(transactionMeta);
148 if (result.has_value())
149 response[jss::nftoken_id] = to_string(result.value());
150 }
151 else if (type == ttNFTOKEN_ACCEPT_OFFER)
152 {
153 std::vector<uint256> result = getNFTokenIDFromDeletedOffer(transactionMeta);
154
155 if (!result.empty())
156 response[jss::nftoken_id] = to_string(result.front());
157 }
158 else if (type == ttNFTOKEN_CANCEL_OFFER)
159 {
160 std::vector<uint256> const result = getNFTokenIDFromDeletedOffer(transactionMeta);
161
162 response[jss::nftoken_ids] = json::Value(json::ValueType::Array);
163 for (auto const& nftID : result)
164 response[jss::nftoken_ids].append(to_string(nftID));
165 }
166}
167
168} // namespace xrpl
T back_inserter(T... args)
Represents a JSON value.
Definition json_value.h:130
Value & append(Value const &value)
Append value to array at the end.
Identifies fields.
Definition SField.h:130
D & downcast()
Definition STBase.h:195
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:678
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
STBase const & peekAtField(SField const &field) const
Definition STObject.cpp:399
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 mismatch(T... args)
@ Array
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:41
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:24
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
void insertNFTokenID(json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
std::optional< uint256 > getNFTokenIDFromPage(TxMeta const &transactionMeta)
Definition NFTokenID.cpp:41
T has_value(T... args)
T push_back(T... args)
T size(T... args)
T sort(T... args)
T transform(T... args)
T unique(T... args)
T value(T... args)