Fixes #769
This commit is contained in:
cyan317
2023-07-26 17:12:20 +01:00
committed by GitHub
parent 6b98579bfb
commit 71aabc8c29
9 changed files with 672 additions and 6 deletions

View File

@@ -24,10 +24,17 @@ jobs:
- uses: actions/checkout@v3
with:
path: clio
#our self-host mac has conan-non-prod configured
- name: List conan artifactory
run: |
conan search
conan remote list
if [[ $(conan remote list |grep conan-non-prod| wc -c) -ne 0 ]]; then
echo "conan-non-prod is available"
else
echo "adding conan-non-prod"
conan remote add conan-non-prod http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod
fi
- name: Install dependencies
run: |

View File

@@ -25,6 +25,7 @@
#include <util/Profiler.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/NFTSyntheticSerializer.h>
#include <ripple/protocol/nftPageMask.h>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
@@ -190,12 +191,26 @@ toJson(ripple::STBase const& obj)
}
std::pair<boost::json::object, boost::json::object>
toExpandedJson(Backend::TransactionAndMetadata const& blobs)
toExpandedJson(Backend::TransactionAndMetadata const& blobs, NFTokenjson nftEnabled)
{
auto [txn, meta] = deserializeTxPlusMeta(blobs, blobs.ledgerSequence);
auto txnJson = toJson(*txn);
auto metaJson = toJson(*meta);
insertDeliveredAmount(metaJson, txn, meta, blobs.date);
if (nftEnabled == NFTokenjson::ENABLE)
{
Json::Value nftJson;
ripple::insertNFTSyntheticInJson(nftJson, txn, *meta);
// if there is no nft fields, the nftJson will be {"meta":null}
auto const nftBoostJson = toBoostJson(nftJson).as_object();
if (nftBoostJson.contains(JS(meta)) and nftBoostJson.at(JS(meta)).is_object())
{
for (auto const& [k, v] : nftBoostJson.at(JS(meta)).as_object())
metaJson.insert_or_assign(k, v);
}
}
return {txnJson, metaJson};
}

View File

@@ -38,6 +38,8 @@
namespace RPC {
enum class NFTokenjson { ENABLE, DISABLE };
std::optional<ripple::AccountID>
accountFromStringStrict(std::string const& account);
@@ -59,7 +61,7 @@ std::pair<std::shared_ptr<ripple::STTx const>, std::shared_ptr<ripple::TxMeta co
deserializeTxPlusMeta(Backend::TransactionAndMetadata const& blobs, std::uint32_t seq);
std::pair<boost::json::object, boost::json::object>
toExpandedJson(Backend::TransactionAndMetadata const& blobs);
toExpandedJson(Backend::TransactionAndMetadata const& blobs, NFTokenjson includeNFTIDs = NFTokenjson::DISABLE);
bool
insertDeliveredAmount(

View File

@@ -113,7 +113,7 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
boost::json::object obj;
if (!input.binary)
{
auto [txn, meta] = toExpandedJson(txnPlusMeta);
auto [txn, meta] = toExpandedJson(txnPlusMeta, NFTokenjson::ENABLE);
obj[JS(meta)] = std::move(meta);
obj[JS(tx)] = std::move(txn);
obj[JS(tx)].as_object()[JS(ledger_index)] = txnPlusMeta.ledgerSequence;

View File

@@ -58,7 +58,7 @@ TxHandler::process(Input input, Context const& ctx) const
// clio does not implement 'inLedger' which is a deprecated field
if (!input.binary)
{
auto const [txn, meta] = toExpandedJson(*dbResponse);
auto const [txn, meta] = toExpandedJson(*dbResponse, NFTokenjson::ENABLE);
output.tx = txn;
output.meta = meta;
}

View File

@@ -33,6 +33,9 @@ constexpr static auto MAXSEQ = 30;
constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
constexpr static auto NFTID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF";
constexpr static auto NFTID2 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA";
constexpr static auto NFTID3 = "15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF";
class RPCAccountTxHandlerTest : public HandlerBaseTest
{
@@ -222,7 +225,9 @@ TEST_P(AccountTxParameterTest, InvalidParams)
});
}
static std::vector<TransactionAndMetadata>
namespace {
std::vector<TransactionAndMetadata>
genTransactions(uint32_t seq1, uint32_t seq2)
{
auto transactions = std::vector<TransactionAndMetadata>{};
@@ -246,6 +251,34 @@ genTransactions(uint32_t seq1, uint32_t seq2)
return transactions;
}
std::vector<TransactionAndMetadata>
genNFTTransactions(uint32_t seq)
{
auto transactions = std::vector<TransactionAndMetadata>{};
auto trans1 = CreateMintNFTTxWithMetadata(ACCOUNT, 1, 50, 123, NFTID);
trans1.ledgerSequence = seq;
trans1.date = 1;
transactions.push_back(trans1);
auto trans2 = CreateAcceptNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID2);
trans2.ledgerSequence = seq;
trans2.date = 2;
transactions.push_back(trans2);
auto trans3 = CreateCancelNFTOffersTxWithMetadata(ACCOUNT, 1, 50, std::vector<std::string>{NFTID2, NFTID3});
trans3.ledgerSequence = seq;
trans3.date = 3;
transactions.push_back(trans3);
auto trans4 = CreateCreateNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID, 123, NFTID2);
trans4.ledgerSequence = seq;
trans4.date = 4;
transactions.push_back(trans4);
return transactions;
}
} // namespace
TEST_F(RPCAccountTxHandlerTest, IndexSpecificForwardTrue)
{
mockBackendPtr->updateRange(MINSEQ); // min
@@ -803,3 +836,228 @@ TEST_F(RPCAccountTxHandlerTest, LimitLessThanMin)
EXPECT_EQ(output->at("transactions").as_array().size(), 2);
});
}
TEST_F(RPCAccountTxHandlerTest, NFTTxs)
{
auto const OUT = R"({
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 10,
"ledger_index_max": 30,
"transactions": [
{
"meta": {
"AffectedNodes":
[
{
"ModifiedNode":
{
"FinalFields":
{
"NFTokens":
[
{
"NFToken":
{
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF",
"URI": "7465737475726C"
}
},
{
"NFToken":
{
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
"URI": "7465737475726C"
}
}
]
},
"LedgerEntryType": "NFTokenPage",
"PreviousFields":
{
"NFTokens":
[
{
"NFToken":
{
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
"URI": "7465737475726C"
}
}
]
}
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"nftoken_id": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
},
"tx":
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "50",
"NFTokenTaxon": 123,
"Sequence": 1,
"SigningPubKey": "74657374",
"TransactionType": "NFTokenMint",
"hash": "C74463F49CFDCBEF3E9902672719918CDE5042DC7E7660BEBD1D1105C4B6DFF4",
"ledger_index": 11,
"date": 1
},
"validated": true
},
{
"meta":
{
"AffectedNodes":
[
{
"DeletedNode":
{
"FinalFields":
{
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
},
"LedgerEntryType": "NFTokenOffer"
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"nftoken_id": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
},
"tx":
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "50",
"NFTokenBuyOffer": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
"Sequence": 1,
"SigningPubKey": "74657374",
"TransactionType": "NFTokenAcceptOffer",
"hash": "7682BE6BCDE62F8142915DD852936623B68FC3839A8A424A6064B898702B0CDF",
"ledger_index": 11,
"date": 2
},
"validated": true
},
{
"meta":
{
"AffectedNodes":
[
{
"DeletedNode": {
"FinalFields":
{
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
},
"LedgerEntryType": "NFTokenOffer"
}
},
{
"DeletedNode":
{
"FinalFields":
{
"NFTokenID": "15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
},
"LedgerEntryType": "NFTokenOffer"
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"nftoken_ids":
[
"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA",
"15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
]
},
"tx":
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "50",
"NFTokenOffers":
[
"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA",
"15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
],
"Sequence": 1,
"SigningPubKey": "74657374",
"TransactionType": "NFTokenCancelOffer",
"hash": "9F82743EEB30065FB9CB92C61F0F064B5859C5A590FA811FAAAD9C988E5B47DB",
"ledger_index": 11,
"date": 3
},
"validated": true
},
{
"meta":
{
"AffectedNodes":
[
{
"CreatedNode":
{
"LedgerEntryType": "NFTokenOffer",
"LedgerIndex": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"offer_id": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
},
"tx":
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Amount": "123",
"Fee": "50",
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF",
"Sequence": 1,
"SigningPubKey": "74657374",
"TransactionType": "NFTokenCreateOffer",
"hash": "ECB1837EB7C7C0AC22ECDCCE59FDD4795C70E0B9D8F4E1C9A9408BB7EC75DA5C",
"ledger_index": 11,
"date": 4
},
"validated": true
}
],
"validated": true,
"marker":
{
"ledger": 12,
"seq": 34
}
})";
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
auto const transactions = genNFTTransactions(MINSEQ + 1);
auto const transCursor = TransactionsAndCursor{transactions, TransactionsCursor{12, 34}};
ON_CALL(*rawBackendPtr, fetchAccountTransactions).WillByDefault(Return(transCursor));
EXPECT_CALL(
*rawBackendPtr,
fetchAccountTransactions(
testing::_, testing::_, false, testing::Optional(testing::Eq(TransactionsCursor{10, 11})), testing::_))
.Times(1);
runSpawn([&, this](auto& yield) {
auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
auto const static input = boost::json::parse(fmt::format(
R"({{
"account": "{}",
"ledger_index_min": {},
"ledger_index_max": {},
"forward": false,
"marker": {{"ledger": 10, "seq": 11}}
}})",
ACCOUNT,
-1,
-1));
auto const output = handler.process(input, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(*output, boost::json::parse(OUT));
});
}

View File

@@ -29,6 +29,8 @@ namespace json = boost::json;
using namespace testing;
constexpr static auto TXNID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
constexpr static auto NFTID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF";
constexpr static auto NFTID2 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA";
constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto CURRENCY = "0158415500000000C1F76FF6ECB0BAC600000000";
@@ -252,3 +254,155 @@ TEST_F(RPCTxTest, ReturnBinary)
EXPECT_EQ(*output, json::parse(OUT));
});
}
TEST_F(RPCTxTest, MintNFT)
{
auto const static OUT = fmt::format(
R"({{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "50",
"NFTokenTaxon": 123,
"Sequence": 1,
"SigningPubKey": "74657374",
"TransactionType": "NFTokenMint",
"hash": "C74463F49CFDCBEF3E9902672719918CDE5042DC7E7660BEBD1D1105C4B6DFF4",
"meta": {{
"AffectedNodes": [
{{
"ModifiedNode": {{
"FinalFields": {{
"NFTokens": [
{{
"NFToken":
{{
"NFTokenID": "{}",
"URI": "7465737475726C"
}}
}},
{{
"NFToken":
{{
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
"URI": "7465737475726C"
}}
}}
]
}},
"LedgerEntryType": "NFTokenPage",
"PreviousFields": {{
"NFTokens": [
{{
"NFToken":
{{
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
"URI": "7465737475726C"
}}
}}
]
}}
}}
}}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"nftoken_id": "{}"
}},
"validated": true,
"date": 123456,
"ledger_index": 100
}})",
NFTID,
NFTID);
TransactionAndMetadata tx = CreateMintNFTTxWithMetadata(ACCOUNT, 1, 50, 123, NFTID);
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
tx.date = 123456;
tx.ledgerSequence = 100;
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
runSpawn([this](auto& yield) {
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
auto const req = json::parse(fmt::format(
R"({{
"command": "tx",
"transaction": "{}"
}})",
TXNID));
auto const output = handler.process(req, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(*output, json::parse(OUT));
});
}
TEST_F(RPCTxTest, NFTAcceptOffer)
{
TransactionAndMetadata tx = CreateAcceptNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID);
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
tx.date = 123456;
tx.ledgerSequence = 100;
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
runSpawn([this](auto& yield) {
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
auto const req = json::parse(fmt::format(
R"({{
"command": "tx",
"transaction": "{}"
}})",
TXNID));
auto const output = handler.process(req, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_EQ(output->at("meta").at("nftoken_id").as_string(), NFTID);
});
}
TEST_F(RPCTxTest, NFTCancelOffer)
{
std::vector<std::string> ids{NFTID, NFTID2};
TransactionAndMetadata tx = CreateCancelNFTOffersTxWithMetadata(ACCOUNT, 1, 50, ids);
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
tx.date = 123456;
tx.ledgerSequence = 100;
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
runSpawn([this, &ids](auto& yield) {
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
auto const req = json::parse(fmt::format(
R"({{
"command": "tx",
"transaction": "{}"
}})",
TXNID));
auto const output = handler.process(req, Context{std::ref(yield)});
ASSERT_TRUE(output);
for (auto const& id : output->at("meta").at("nftoken_ids").as_array())
{
auto const idStr = id.as_string();
ids.erase(std::find(ids.begin(), ids.end(), std::string{idStr.c_str(), idStr.size()}));
}
EXPECT_TRUE(ids.empty());
});
}
TEST_F(RPCTxTest, NFTCreateOffer)
{
TransactionAndMetadata tx = CreateCreateNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID, 123, NFTID2);
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
tx.date = 123456;
tx.ledgerSequence = 100;
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
runSpawn([this](auto& yield) {
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
auto const req = json::parse(fmt::format(
R"({{
"command": "tx",
"transaction": "{}"
}})",
TXNID));
auto const output = handler.process(req, Context{std::ref(yield)});
ASSERT_TRUE(output);
EXPECT_TRUE(output->at("meta").at("offer_id").as_string() == NFTID2);
});
}

View File

@@ -19,12 +19,15 @@
#include "TestObject.h"
#include <backend/DBHelpers.h>
#include <rpc/RPCHelpers.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/TER.h>
#include <chrono>
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
ripple::AccountID
GetAccountIDWithString(std::string_view id)
{
@@ -529,3 +532,200 @@ CreateNFTTokenPage(
tokenPage.setFieldArray(ripple::sfNFTokens, list);
return tokenPage;
}
Backend::TransactionAndMetadata
CreateMintNFTTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
uint32_t nfTokenTaxon,
std::string_view nftID)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_MINT);
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
tx.setAccountID(ripple::sfAccount, account.value());
auto amount = ripple::STAmount(fee, false);
tx.setFieldAmount(ripple::sfFee, amount);
// required field for ttNFTOKEN_MINT
tx.setFieldU32(ripple::sfNFTokenTaxon, nfTokenTaxon);
tx.setFieldU32(ripple::sfSequence, seq);
const char* key = "test";
ripple::Slice slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
// meta
ripple::STObject metaObj(ripple::sfTransactionMetaData);
ripple::STArray metaArray{1};
ripple::STObject node(ripple::sfModifiedNode);
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_PAGE);
ripple::STObject finalFields(ripple::sfFinalFields);
ripple::STArray NFTArray1{2};
// finalFields contain new NFT while previousFields does not
auto entry = ripple::STObject(ripple::sfNFToken);
entry.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
const char* url = "testurl";
entry.setFieldVL(ripple::sfURI, ripple::Slice(url, 7));
NFTArray1.push_back(entry);
auto entry2 = ripple::STObject(ripple::sfNFToken);
entry2.setFieldH256(ripple::sfNFTokenID, ripple::uint256{INDEX1});
entry2.setFieldVL(ripple::sfURI, ripple::Slice(url, 7));
NFTArray1.push_back(entry2);
finalFields.setFieldArray(ripple::sfNFTokens, NFTArray1);
NFTArray1.erase(NFTArray1.begin());
ripple::STObject previousFields(ripple::sfPreviousFields);
previousFields.setFieldArray(ripple::sfNFTokens, NFTArray1);
node.emplace_back(std::move(finalFields));
node.emplace_back(std::move(previousFields));
metaArray.push_back(node);
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
Backend::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
Backend::TransactionAndMetadata
CreateAcceptNFTOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view nftId)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_ACCEPT_OFFER);
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
tx.setAccountID(ripple::sfAccount, account.value());
auto amount = ripple::STAmount(fee, false);
tx.setFieldAmount(ripple::sfFee, amount);
tx.setFieldU32(ripple::sfSequence, seq);
tx.setFieldH256(ripple::sfNFTokenBuyOffer, ripple::uint256{INDEX1});
const char* key = "test";
ripple::Slice slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
// meta
// create deletedNode with ltNFTOKEN_OFFER
ripple::STObject metaObj(ripple::sfTransactionMetaData);
ripple::STArray metaArray{1};
ripple::STObject node(ripple::sfDeletedNode);
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_OFFER);
ripple::STObject finalFields(ripple::sfFinalFields);
finalFields.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId});
node.emplace_back(std::move(finalFields));
metaArray.push_back(node);
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
Backend::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
// NFTokenCancelOffer can be used to cancel multiple offers
Backend::TransactionAndMetadata
CreateCancelNFTOffersTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::vector<std::string> const& nftOffers)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_CANCEL_OFFER);
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
tx.setAccountID(ripple::sfAccount, account.value());
auto amount = ripple::STAmount(fee, false);
tx.setFieldAmount(ripple::sfFee, amount);
tx.setFieldU32(ripple::sfSequence, seq);
ripple::STVector256 offers;
offers.resize(2);
std::transform(nftOffers.cbegin(), nftOffers.cend(), offers.begin(), [&](auto const& nftId) {
return ripple::uint256{nftId.c_str()};
});
tx.setFieldV256(ripple::sfNFTokenOffers, offers);
const char* key = "test";
ripple::Slice slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
// meta
// create deletedNode with ltNFTOKEN_OFFER
// reuse the offer id as nft id
ripple::STObject metaObj(ripple::sfTransactionMetaData);
ripple::STArray metaArray{nftOffers.size()};
for (auto const& nftId : nftOffers)
{
ripple::STObject node(ripple::sfDeletedNode);
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_OFFER);
ripple::STObject finalFields(ripple::sfFinalFields);
finalFields.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId.c_str()});
node.emplace_back(std::move(finalFields));
metaArray.push_back(node);
}
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
Backend::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
Backend::TransactionAndMetadata
CreateCreateNFTOfferTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::string_view nftId,
std::uint32_t offerPrice,
std::string_view offerId)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_CREATE_OFFER);
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
tx.setAccountID(ripple::sfAccount, account.value());
auto amount = ripple::STAmount(fee, false);
tx.setFieldAmount(ripple::sfFee, amount);
auto price = ripple::STAmount(offerPrice, false);
tx.setFieldAmount(ripple::sfAmount, price);
tx.setFieldU32(ripple::sfSequence, seq);
tx.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId});
const char* key = "test";
ripple::Slice slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
// meta
// create createdNode with LedgerIndex
ripple::STObject metaObj(ripple::sfTransactionMetaData);
ripple::STArray metaArray{1};
ripple::STObject node(ripple::sfCreatedNode);
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_OFFER);
node.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{offerId});
metaArray.push_back(node);
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
Backend::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}

View File

@@ -228,3 +228,33 @@ CreateSignerLists(std::vector<std::pair<std::string, uint32_t>> const& signers);
CreateNFTTokenPage(
std::vector<std::pair<std::string, std::string>> const& tokens,
std::optional<ripple::uint256> previousPage);
[[nodiscard]] Backend::TransactionAndMetadata
CreateMintNFTTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
uint32_t nfTokenTaxon,
std::string_view nftID);
[[nodiscard]] Backend::TransactionAndMetadata
CreateAcceptNFTOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view offerId);
[[nodiscard]] Backend::TransactionAndMetadata
CreateCancelNFTOffersTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::vector<std::string> const& nftIds);
[[nodiscard]] Backend::TransactionAndMetadata
CreateCreateNFTOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view offerId);
[[nodiscard]] Backend::TransactionAndMetadata
CreateCreateNFTOfferTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::string_view nftId,
std::uint32_t offerPrice,
std::string_view offerId);