#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { bool canHaveNFTokenID(std::shared_ptr const& serializedTx, TxMeta const& transactionMeta) { if (!serializedTx) return false; TxType const tt = serializedTx->getTxnType(); if (tt != ttNFTOKEN_MINT && tt != ttNFTOKEN_ACCEPT_OFFER && tt != ttNFTOKEN_CANCEL_OFFER) return false; // if the transaction failed nothing could have been delivered. if (!isTesSuccess(transactionMeta.getResultTER())) return false; return true; } std::optional getNFTokenIDFromPage(TxMeta const& transactionMeta) { // The metadata does not make it obvious which NFT was added. To figure // that out we gather up all of the previous NFT IDs and all of the final // NFT IDs and compare them to find what changed. std::vector prevIDs; std::vector finalIDs; for (STObject const& node : transactionMeta.getNodes()) { if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_PAGE) continue; SField const& fName = node.getFName(); if (fName == sfCreatedNode) { STArray const& toAddPrevNFTs = node.peekAtField(sfNewFields).downcast().getFieldArray(sfNFTokens); std::transform( toAddPrevNFTs.begin(), toAddPrevNFTs.end(), std::back_inserter(finalIDs), [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); }); } else if (fName == sfModifiedNode) { // When a mint results in splitting an existing page, // it results in a created page and a modified node. Sometimes, // the created node needs to be linked to a third page, resulting // in modifying that third page's PreviousPageMin or NextPageMin // field changing, but no NFTs within that page changing. In this // case, there will be no previous NFTs and we need to skip. // However, there will always be NFTs listed in the final fields, // as xrpld outputs all fields in final fields even if they were // not changed. STObject const& previousFields = node.peekAtField(sfPreviousFields).downcast(); if (!previousFields.isFieldPresent(sfNFTokens)) continue; STArray const& toAddPrevNFTs = previousFields.getFieldArray(sfNFTokens); std::transform( toAddPrevNFTs.begin(), toAddPrevNFTs.end(), std::back_inserter(prevIDs), [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); }); STArray const& toAddFinalNFTs = node.peekAtField(sfFinalFields).downcast().getFieldArray(sfNFTokens); std::transform( toAddFinalNFTs.begin(), toAddFinalNFTs.end(), std::back_inserter(finalIDs), [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); }); } } // We expect NFTs to be added one at a time. So finalIDs should be one // longer than prevIDs. If that's not the case something is messed up. if (finalIDs.size() != prevIDs.size() + 1) return std::nullopt; // Find the first NFT ID that doesn't match. We're looking for an // added NFT, so the one we want will be the mismatch in finalIDs. auto const diff = std::mismatch(finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end()); // There should always be a difference so the returned finalIDs // iterator should never be end(). But better safe than sorry. if (diff.first == finalIDs.end()) return std::nullopt; return *diff.first; } std::vector getNFTokenIDFromDeletedOffer(TxMeta const& transactionMeta) { std::vector tokenIDResult; for (STObject const& node : transactionMeta.getNodes()) { if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_OFFER || node.getFName() != sfDeletedNode) continue; auto const& toAddNFT = node.peekAtField(sfFinalFields).downcast().getFieldH256(sfNFTokenID); tokenIDResult.push_back(toAddNFT); } // Deduplicate the NFT IDs because multiple offers could affect the same NFT // and hence we would get duplicate NFT IDs sort(tokenIDResult.begin(), tokenIDResult.end()); tokenIDResult.erase(unique(tokenIDResult.begin(), tokenIDResult.end()), tokenIDResult.end()); return tokenIDResult; } void insertNFTokenID( Json::Value& response, std::shared_ptr const& transaction, TxMeta const& transactionMeta) { if (!canHaveNFTokenID(transaction, transactionMeta)) return; // We extract the NFTokenID from metadata by comparing affected nodes if (auto const type = transaction->getTxnType(); type == ttNFTOKEN_MINT) { std::optional result = getNFTokenIDFromPage(transactionMeta); if (result.has_value()) response[jss::nftoken_id] = to_string(result.value()); } else if (type == ttNFTOKEN_ACCEPT_OFFER) { std::vector result = getNFTokenIDFromDeletedOffer(transactionMeta); if (!result.empty()) response[jss::nftoken_id] = to_string(result.front()); } else if (type == ttNFTOKEN_CANCEL_OFFER) { std::vector const result = getNFTokenIDFromDeletedOffer(transactionMeta); response[jss::nftoken_ids] = Json::Value(Json::arrayValue); for (auto const& nftID : result) response[jss::nftoken_ids].append(to_string(nftID)); } } } // namespace xrpl