feat: Support Dynamic NFT (#1525)

Fix #1471
Clio's changes for supporting DNFT
https://github.com/XRPLF/rippled/pull/5048/files
This commit is contained in:
cyan317
2025-01-31 13:33:20 +00:00
committed by GitHub
parent e7702e9c11
commit 1753c95910
16 changed files with 952 additions and 50 deletions

View File

@@ -728,7 +728,230 @@ createMintNftTxWithMetadata(
}
data::TransactionAndMetadata
createAcceptNftOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view nftId)
createMintNftTxWithMetadataOfCreatedNode(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
uint32_t nfTokenTaxon,
std::optional<std::string_view> nftID,
std::optional<std::string_view> uri,
std::optional<std::string_view> pageIndex
)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_MINT);
auto account = util::parseBase58Wrapper<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);
char const* key = "test";
ripple::Slice const slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
if (uri)
tx.setFieldVL(ripple::sfURI, ripple::Slice(uri->data(), uri->size()));
// meta
ripple::STObject metaObj(ripple::sfTransactionMetaData);
ripple::STArray metaArray{1};
ripple::STObject node(ripple::sfCreatedNode);
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_PAGE);
ripple::STObject newFields(ripple::sfNewFields);
ripple::STArray NFTArray1{1};
if (nftID) {
// finalFields contain new NFT while previousFields does not
auto entry = ripple::STObject(ripple::sfNFToken);
entry.setFieldH256(ripple::sfNFTokenID, ripple::uint256{*nftID});
if (uri)
entry.setFieldVL(ripple::sfURI, ripple::Slice(uri->data(), uri->size()));
NFTArray1.push_back(entry);
}
newFields.setFieldArray(ripple::sfNFTokens, NFTArray1);
node.emplace_back(std::move(newFields));
if (pageIndex)
node.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{*pageIndex});
// add a ledger object ahead of nft page
ripple::STObject node2(ripple::sfCreatedNode);
node2.setFieldU16(ripple::sfLedgerEntryType, ripple::ltACCOUNT_ROOT);
metaArray.push_back(node2);
metaArray.push_back(node);
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
data::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
data::TransactionAndMetadata
createNftModifyTxWithMetadata(std::string_view accountId, std::string_view nftID, ripple::Blob uri)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_MODIFY);
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
tx.setAccountID(ripple::sfAccount, account.value());
auto amount = ripple::STAmount(10, false);
tx.setFieldAmount(ripple::sfFee, amount);
tx.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
tx.setFieldU32(ripple::sfSequence, 100);
char const* key = "test";
ripple::Slice const slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
if (!uri.empty()) // sfURI should be absent if empty
tx.setFieldVL(ripple::sfURI, uri);
// 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{1};
ripple::STArray NFTArray2{1};
// finalFields contain new NFT while previousFields does not
auto entry = ripple::STObject(ripple::sfNFToken);
entry.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
if (!uri.empty())
entry.setFieldVL(ripple::sfURI, uri);
NFTArray1.push_back(entry);
auto entry2 = ripple::STObject(ripple::sfNFToken);
entry2.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
char const* url = "previous";
entry2.setFieldVL(ripple::sfURI, ripple::Slice(url, 7));
NFTArray2.push_back(entry2);
finalFields.setFieldArray(ripple::sfNFTokens, NFTArray1);
ripple::STObject previousFields(ripple::sfPreviousFields);
previousFields.setFieldArray(ripple::sfNFTokens, NFTArray2);
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);
data::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
data::TransactionAndMetadata
createNftBurnTxWithMetadataOfDeletedNode(std::string_view accountId, std::string_view nftID)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_BURN);
auto account = getAccountIdWithString(accountId);
tx.setAccountID(ripple::sfAccount, account);
auto amount = ripple::STAmount(10, false);
tx.setFieldAmount(ripple::sfFee, amount);
tx.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
tx.setFieldU32(ripple::sfSequence, 100);
char const* key = "test";
ripple::Slice const slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
// meta
ripple::STObject metaObj(ripple::sfTransactionMetaData);
ripple::STArray metaArray{1};
ripple::STObject node(ripple::sfDeletedNode);
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_PAGE);
// deleted node should contain finalFields
ripple::STObject finalFields(ripple::sfFinalFields);
ripple::STArray NFTArray{1};
auto entry = ripple::STObject(ripple::sfNFToken);
entry.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
NFTArray.push_back(entry);
finalFields.setFieldArray(ripple::sfNFTokens, NFTArray);
node.emplace_back(std::move(finalFields));
// add a ledger object ahead of nft page
ripple::STObject node2(ripple::sfCreatedNode);
node2.setFieldU16(ripple::sfLedgerEntryType, ripple::ltACCOUNT_ROOT);
metaArray.push_back(node2);
metaArray.push_back(node);
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
data::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
data::TransactionAndMetadata
createNftBurnTxWithMetadataOfModifiedNode(std::string_view accountId, std::string_view nftID)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_BURN);
auto account = getAccountIdWithString(accountId);
tx.setAccountID(ripple::sfAccount, account);
auto amount = ripple::STAmount(10, false);
tx.setFieldAmount(ripple::sfFee, amount);
tx.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
tx.setFieldU32(ripple::sfSequence, 100);
char const* key = "test";
ripple::Slice const 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 NFTArray{1};
ripple::STObject previousFields(ripple::sfPreviousFields);
auto entry = ripple::STObject(ripple::sfNFToken);
entry.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
NFTArray.push_back(entry);
previousFields.setFieldArray(ripple::sfNFTokens, NFTArray);
node.emplace_back(std::move(previousFields));
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);
data::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
data::TransactionAndMetadata
createAcceptNftBuyerOfferTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::string_view nftId,
std::string_view offerId
)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
@@ -738,7 +961,7 @@ createAcceptNftOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uin
auto amount = ripple::STAmount(fee, false);
tx.setFieldAmount(ripple::sfFee, amount);
tx.setFieldU32(ripple::sfSequence, seq);
tx.setFieldH256(ripple::sfNFTokenBuyOffer, ripple::uint256{kINDEX1});
tx.setFieldH256(ripple::sfNFTokenBuyOffer, ripple::uint256{offerId});
char const* key = "test";
ripple::Slice const slice(key, 4);
tx.setFieldVL(ripple::sfSigningPubKey, slice);
@@ -752,8 +975,11 @@ createAcceptNftOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uin
ripple::STObject finalFields(ripple::sfFinalFields);
finalFields.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId});
// for buyer offer, the offer owner is the nft's new owner
finalFields.setAccountID(ripple::sfOwner, account.value());
node.emplace_back(std::move(finalFields));
node.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{offerId});
metaArray.push_back(node);
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
@@ -765,6 +991,99 @@ createAcceptNftOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uin
return ret;
}
data::TransactionAndMetadata
createAcceptNftSellerOfferTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::string_view nftId,
std::string_view offerId,
std::string_view pageIndex,
bool isNewPageCreated
)
{
// tx
ripple::STObject tx(ripple::sfTransaction);
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_ACCEPT_OFFER);
auto account = util::parseBase58Wrapper<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::sfNFTokenSellOffer, ripple::uint256{offerId});
char const* key = "test";
ripple::Slice const 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});
// offer owner is not the nft's new owner for seller offer, we need to create other nodes for processing new owner
finalFields.setAccountID(ripple::sfOwner, account.value());
node.emplace_back(std::move(finalFields));
node.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{offerId});
metaArray.push_back(node);
// new owner's nft page node changed: 1 new nft page node added 2 old nft page node modified
if (isNewPageCreated) {
ripple::STObject node2(ripple::sfCreatedNode);
node2.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_PAGE);
ripple::STObject newFields(ripple::sfNewFields);
ripple::STArray nftArray1{1};
auto entry = ripple::STObject(ripple::sfNFToken);
entry.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId});
nftArray1.push_back(entry);
newFields.setFieldArray(ripple::sfNFTokens, nftArray1);
node2.emplace_back(std::move(newFields));
node2.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{pageIndex});
metaArray.push_back(node2);
} else {
ripple::STObject node2(ripple::sfModifiedNode);
node2.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_PAGE);
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});
nftArray1.push_back(entry);
auto entry2 = ripple::STObject(ripple::sfNFToken);
entry2.setFieldH256(ripple::sfNFTokenID, ripple::uint256{kINDEX1});
nftArray1.push_back(entry2);
finalFields.setFieldArray(ripple::sfNFTokens, nftArray1);
nftArray1.erase(nftArray1.begin());
ripple::STObject previousFields(ripple::sfPreviousFields);
previousFields.setFieldArray(ripple::sfNFTokens, nftArray1);
node2.emplace_back(std::move(finalFields));
node2.emplace_back(std::move(previousFields));
node2.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{pageIndex});
metaArray.push_back(node2);
}
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
data::TransactionAndMetadata ret;
ret.transaction = tx.getSerializer().peekData();
ret.metadata = metaObj.getSerializer().peekData();
return ret;
}
// NFTokenCancelOffer can be used to cancel multiple offers
data::TransactionAndMetadata
createCancelNftOffersTxWithMetadata(

View File

@@ -302,6 +302,9 @@ createNftTokenPage(
std::optional<ripple::uint256> previousPage
);
/**
* Create NFToken mint tx, the metadata contained a changed node
*/
[[nodiscard]] data::TransactionAndMetadata
createMintNftTxWithMetadata(
std::string_view accountId,
@@ -311,8 +314,54 @@ createMintNftTxWithMetadata(
std::string_view nftID
);
/**
* Create NFToken mint tx, the metadata contained a created node
*/
[[nodiscard]] data::TransactionAndMetadata
createAcceptNftOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view nftId);
createMintNftTxWithMetadataOfCreatedNode(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
uint32_t nfTokenTaxon,
std::optional<std::string_view> nftID,
std::optional<std::string_view> uri,
std::optional<std::string_view> pageIndex
);
[[nodiscard]] data::TransactionAndMetadata
createNftModifyTxWithMetadata(std::string_view accountId, std::string_view nftID, ripple::Blob uri);
/**
* Create NFToken burn tx, tx causes a nft page node deleted
*/
[[nodiscard]] data::TransactionAndMetadata
createNftBurnTxWithMetadataOfDeletedNode(std::string_view accountId, std::string_view nftID);
/**
* Create NFToken mint tx, tx causes a nft page node changed
*/
[[nodiscard]] data::TransactionAndMetadata
createNftBurnTxWithMetadataOfModifiedNode(std::string_view accountId, std::string_view nftID);
[[nodiscard]] data::TransactionAndMetadata
createAcceptNftBuyerOfferTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::string_view nftId,
std::string_view offerId
);
[[nodiscard]] data::TransactionAndMetadata
createAcceptNftSellerOfferTxWithMetadata(
std::string_view accountId,
uint32_t seq,
uint32_t fee,
std::string_view nftId,
std::string_view offerId,
std::string_view pageIndex,
bool isNewPageCreated
);
[[nodiscard]] data::TransactionAndMetadata
createCancelNftOffersTxWithMetadata(
@@ -322,9 +371,6 @@ createCancelNftOffersTxWithMetadata(
std::vector<std::string> const& nftOffers
);
[[nodiscard]] data::TransactionAndMetadata
createCreateNftOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view offerId);
[[nodiscard]] data::TransactionAndMetadata
createCreateNftOfferTxWithMetadata(
std::string_view accountId,