Skip to content

Commit

Permalink
support DynamicNFT XLS-46 (#799)
Browse files Browse the repository at this point in the history
* add nfts_by_issuer

* update Changelog

* added nfts_by_issuer to Request's get_method

* reformat changelog and add required comment for nfts_by_issuer.py

* add include_delete field

* update change log

* another conflict fixed

* create DNFT support

* update changelog

* fix dynamic nft

* add more test

* resolve comments

* resolve comment

---------

Co-authored-by: Kassaking <[email protected]>
Co-authored-by: Kassaking7 <[email protected]>
Co-authored-by: Zhiyuan Wang <[email protected]>
  • Loading branch information
4 people authored Feb 6, 2025
1 parent eab5f67 commit a12b672
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `nfts_by_issuer` clio-only API definition
- Included `ctid` field in the `tx` request.
- `from_xrpl` method accepts input dictionary keys exclusively in the proper XRPL format.
- Support for DynamicNFT amendment (XLS-46)

### Fixed
- Added support for `XChainModifyBridge` flag maps (fixing an issue with `NFTokenCreateOffer` flag names)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def test_nftoken_mint_flags(self):
TF_ONLY_XRP=True,
TF_TRANSFERABLE=True,
TF_TRUSTLINE=True,
TF_MUTABLE=True,
),
)
self.assertTrue(actual.has_flag(flag=0x00000001))
Expand Down
82 changes: 82 additions & 0 deletions tests/unit/models/transactions/test_nftoken_modify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from unittest import TestCase

from xrpl.models.exceptions import XRPLModelException
from xrpl.models.transactions.nftoken_modify import NFTokenModify

_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
_SEQUENCE = 19048
_FEE = "0.00001"
_URI = "ABC"
_NFTOKEN_ID = "00090032B5F762798A53D543A014CAF8B297CFF8F2F937E844B17C9E00000003"


class TestNFTokenModify(TestCase):
def test_nftoken_miss(self):
with self.assertRaises(XRPLModelException) as error:
NFTokenModify(
account=_ACCOUNT,
owner=_ACCOUNT,
sequence=_SEQUENCE,
fee=_FEE,
uri=_URI,
)
self.assertEqual(
error.exception.args[0],
"{'nftoken_id': 'nftoken_id is not set'}",
)

def test_uri_empty(self):
with self.assertRaises(XRPLModelException) as error:
NFTokenModify(
account=_ACCOUNT,
owner=_ACCOUNT,
sequence=_SEQUENCE,
fee=_FEE,
nftoken_id=_NFTOKEN_ID,
uri="",
)
self.assertEqual(
error.exception.args[0],
"{'uri': 'URI must not be empty string'}",
)

def test_uri_too_long(self):
with self.assertRaises(XRPLModelException) as error:
NFTokenModify(
account=_ACCOUNT,
owner=_ACCOUNT,
sequence=_SEQUENCE,
fee=_FEE,
nftoken_id=_NFTOKEN_ID,
uri=_URI * 1000,
)
self.assertEqual(
error.exception.args[0],
"{'uri': 'URI must not be longer than 512 characters'}",
)

def test_uri_not_hex(self):
with self.assertRaises(XRPLModelException) as error:
NFTokenModify(
account=_ACCOUNT,
owner=_ACCOUNT,
sequence=_SEQUENCE,
fee=_FEE,
nftoken_id=_NFTOKEN_ID,
uri="not-hex-encoded",
)
self.assertEqual(
error.exception.args[0],
"{'uri': 'URI must be encoded in hex'}",
)

def test_valid(self):
obj = NFTokenModify(
account=_ACCOUNT,
owner=_ACCOUNT,
sequence=_SEQUENCE,
fee=_FEE,
uri=_URI,
nftoken_id=_NFTOKEN_ID,
)
self.assertTrue(obj.is_valid())
3 changes: 2 additions & 1 deletion xrpl/core/binarycodec/definitions/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3085,6 +3085,7 @@
"NFTokenCancelOffer": 28,
"NFTokenCreateOffer": 27,
"NFTokenMint": 25,
"NFTokenModify": 61,
"OfferCancel": 8,
"OfferCreate": 7,
"OracleDelete": 52,
Expand Down Expand Up @@ -3138,4 +3139,4 @@
"Vector256": 19,
"XChainBridge": 25
}
}
}
2 changes: 2 additions & 0 deletions xrpl/models/transactions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
NFTokenMintFlag,
NFTokenMintFlagInterface,
)
from xrpl.models.transactions.nftoken_modify import NFTokenModify
from xrpl.models.transactions.offer_cancel import OfferCancel
from xrpl.models.transactions.offer_create import (
OfferCreate,
Expand Down Expand Up @@ -161,6 +162,7 @@
"NFTokenMint",
"NFTokenMintFlag",
"NFTokenMintFlagInterface",
"NFTokenModify",
"OfferCancel",
"OfferCreate",
"OfferCreateFlag",
Expand Down
6 changes: 6 additions & 0 deletions xrpl/models/transactions/nftoken_mint.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class NFTokenMintFlag(int, Enum):
issuer.
"""

TF_MUTABLE = 0x00000010
"""
If set, indicates that this NFT's URI can be modified.
"""


class NFTokenMintFlagInterface(FlagInterface):
"""Transaction Flags for an NFTokenMint Transaction."""
Expand All @@ -54,6 +59,7 @@ class NFTokenMintFlagInterface(FlagInterface):
TF_ONLY_XRP: bool
TF_TRUSTLINE: bool
TF_TRANSFERABLE: bool
TF_MUTABLE: bool


@require_kwargs_on_init
Expand Down
72 changes: 72 additions & 0 deletions xrpl/models/transactions/nftoken_modify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Model for NFTokenModify transaction type."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, Optional

from typing_extensions import Final, Self

from xrpl.models.required import REQUIRED
from xrpl.models.transactions.transaction import Transaction
from xrpl.models.transactions.types import TransactionType
from xrpl.models.utils import HEX_REGEX, KW_ONLY_DATACLASS, require_kwargs_on_init

_MAX_URI_LENGTH: Final[int] = 512


@require_kwargs_on_init
@dataclass(frozen=True, **KW_ONLY_DATACLASS)
class NFTokenModify(Transaction):
"""
The NFTokenModify transaction modifies an NFToken's URI
if its tfMutable is set to true.
"""

nftoken_id: str = REQUIRED # type: ignore
"""
Identifies the TokenID of the NFToken object that the
offer references. This field is required.
"""

owner: Optional[str] = None
"""
Indicates the AccountID of the account that owns the
corresponding NFToken.
"""

uri: Optional[str] = None
"""
URI that points to the data and/or metadata associated with the NFT.
This field need not be an HTTP or HTTPS URL; it could be an IPFS URI, a
magnet link, immediate data encoded as an RFC2379 "data" URL, or even an
opaque issuer-specific encoding. The URI is not checked for validity.
This field must be hex-encoded. You can use `xrpl.utils.str_to_hex` to
convert a UTF-8 string to hex.
"""

transaction_type: TransactionType = field(
default=TransactionType.NFTOKEN_MODIFY,
init=False,
)

def _get_errors(self: Self) -> Dict[str, str]:
return {
key: value
for key, value in {
**super()._get_errors(),
"uri": self._get_uri_error(),
}.items()
if value is not None
}

def _get_uri_error(self: Self) -> Optional[str]:
if not self.uri:
return "URI must not be empty string"
elif len(self.uri) > _MAX_URI_LENGTH:
return f"URI must not be longer than {_MAX_URI_LENGTH} characters"

if not HEX_REGEX.fullmatch(self.uri):
return "URI must be encoded in hex"
return None
1 change: 1 addition & 0 deletions xrpl/models/transactions/types/transaction_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class TransactionType(str, Enum):
NFTOKEN_CANCEL_OFFER = "NFTokenCancelOffer"
NFTOKEN_CREATE_OFFER = "NFTokenCreateOffer"
NFTOKEN_MINT = "NFTokenMint"
NFTOKEN_MODIFY = "NFTokenModify"
OFFER_CANCEL = "OfferCancel"
OFFER_CREATE = "OfferCreate"
ORACLE_SET = "OracleSet"
Expand Down

0 comments on commit a12b672

Please sign in to comment.