Skip to content

Commit

Permalink
feat(ISC-DOC): decode metadata uri
Browse files Browse the repository at this point in the history
  • Loading branch information
Ginowine committed Jun 6, 2024
1 parent fde2ca3 commit 0f6293c
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 78 deletions.
5 changes: 5 additions & 0 deletions docs/build/isc/v1.1/docs/_admonitions/_ERC721.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:::info ERC721

As your L1 NFT is always registered as [ERC721](https://eips.ethereum.org/EIPS/eip-721), you might want to get the metadata like `tokenURI` from there. Using `getIRC27NFTData` is normally only needed if you need special [IRC27](https://wiki.iota.org/tips/tips/TIP-0027/) metadata.

:::
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
:::tip Mint an NFT

Mint your first NFT following our how to [mint an NFT guide](/isc/how-tos/core-contracts/nft/mint-nft/#about-nfts).
Mint your first NFT following our how to [mint an NFT guide](../how-tos/core-contracts/nft/mint-nft.md#about-nfts).

:::
236 changes: 159 additions & 77 deletions docs/build/isc/v1.1/docs/how-tos/core-contracts/nft/get-nft-metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,19 @@ tags:
- how-to
---
import GetNftMetadata from '../../../_partials/how-tos/token/_get-nft-metadata.md';
import ERC721Admonition from '../../../_admonitions/_ERC721.md';

# Get NFT Metadata

<ERC721Admonition />

This guide explains how to use the [`getIRC27NFTData`](../../../reference/magic-contract/ISCSandbox.md#getirc27nftdata) function within a smart contract to fetch information about a specific IRC27 NFT on the IOTA Network.

<GetNftMetadata />

## Understanding the `getIRC27NFTData` Function

The `getIRC27NFTData` function retrieves metadata for an IRC27 NFT based on its identifier. IRC27 is a series of standards to support interoperable and universal NFT systems throughout the IOTA ecosystem.

### Function Signature:

```solidity
function getIRC27NFTData(bytes memory nftID) public view returns (IRC27NFT memory irc27NftData);
```

### Parameters:

`NFTID id`: This parameter represents the unique identifier of the IRC27 NFT you intend to retrieve information about.

### Return Value:

The function returns a data structure of type IRC27NFT. This structure incorporates two elements:
* `nft`: This element provides details regarding the underlying on-chain NFT. It's of type `ISCNFT`.
* `metadata`: This element offers information specific to the IRC27 standard. It's of type `IRC27NFTMetadata`.
The [`getIRC27NFTData`](../../../reference/magic-contract/ISCSandbox.md#getirc27nftdata) function retrieves metadata for an IRC27 NFT based on its identifier. IRC27 is a series of standards to support interoperable and universal NFT systems throughout the IOTA ecosystem.

## How To Use `getIRC27NFTData`

Expand All @@ -56,42 +43,92 @@ The `IRC27NFTMetadata` struct holds detailed metadata, including the URI and oth
struct IRC27NFTMetadata {string uri; string name;string description;}
```

### 3. Implement URI Encoding

The [OpenZeppelin Contracts](https://www.openzeppelin.com/) library provides a `Base64` utility that simplifies encoding URI data. Here's how to implement URI encoding:
### 3. Decode the URI Data in IRC27NFTMetadata

The `IRC27NFTMetadata` struct holds detailed metadata, including the URI and other fields like name and description. Decoding this data correctly is crucial for ensuring the integrity and accessibility of the NFT's metadata.

```solidity
function encodeNFTMetadata(string memory _standard, string memory _version,
string memory _mimeType,
string memory _uri,
string memory _name,
string memory _description
) public pure returns (IRC27NFTMetadata memory)
{
string memory json = string(abi.encodePacked(
'{"name":"', _name,
'","description":"', _description,
'","image":"', _uri,
'"}'
));
string memory encodedURI = string(abi.encodePacked(
"data:application/json;base64,",
Base64.encode(bytes(json))
));
function decodeNFTMetadataURI(string memory encodedURI) public pure returns (IRC27NFTMetadata memory) {
// Ensure the encodedURI has the correct prefix
require(bytes(encodedURI).length > 29, "Invalid encoded URI length");
string memory base64JSON = substring(encodedURI, 29, bytes(encodedURI).length);
// Decode the Base64-encoded JSON
bytes memory decodedBytes = Base64.decode(base64JSON);
string memory json = string(decodedBytes);
// Extract fields from the JSON string
string memory name = extractValueFromJSON(json, "name");
string memory description = extractValueFromJSON(json, "description");
string memory uri = extractValueFromJSON(json, "image");
return IRC27NFTMetadata({
standard: _standard,
version: _version,
mimeType: _mimeType,
standard: "IRC27",
version: "1.0",
mimeType: "application/json",
uri: encodedURI,
name: _name,
description: _description
name: name,
description: description
});
}
```

### `substring` Function

The substring function extracts a substring from a given string based on specified start and end indices. This is useful for isolating the Base64-encoded JSON part of the URI.

```solidity
function substring(string memory str, uint startIndex, uint endIndex) internal pure returns (string memory) {
bytes memory strBytes = bytes(str);
bytes memory result = new bytes(endIndex - startIndex);
for (uint i = startIndex; i < endIndex; i++) {
result[i - startIndex] = strBytes[i];
}
return string(result);
}
```

### `extractValueFromJSON` Function

The `extractValueFromJSON` function extracts the value associated with a specific key from a JSON string. This function helps to retrieve individual fields like name, description, and image from the decoded JSON metadata.

```solidity
function extractValueFromJSON(string memory json, string memory key) internal pure returns (string memory) {
bytes memory jsonBytes = bytes(json);
bytes memory keyBytes = bytes(key);
bytes memory result = new bytes(jsonBytes.length);
uint256 resultIndex = 0;
bool keyFound = false;
for (uint256 i = 0; i < jsonBytes.length; i++) {
if (jsonBytes[i] == keyBytes[0]) {
uint256 j = 0;
while (j < keyBytes.length && jsonBytes[i + j] == keyBytes[j]) {
j++;
}
if (j == keyBytes.length && jsonBytes[i + j] == ':') {
keyFound = true;
i += j + 1;
}
}
if (keyFound && jsonBytes[i] == '"') {
i++;
while (jsonBytes[i] != '"') {
result[resultIndex++] = jsonBytes[i++];
}
break;
}
}
return string(result);
}
```

## Full Example Contract

Combining all the above steps, here’s a complete example:
Expand All @@ -105,47 +142,92 @@ import "@iota/iscmagic/ISC.sol";
import "@iota/iscmagic/ISCTypes.sol";
contract NFTMetadata {
struct IRC27NFT {
bytes id;
string uri;
string name;
string description;
}
function fetchNFTData(bytes memory nftID) public view returns (IRC27NFT memory irc27NftData) {
struct IRC27NFTMetadata {
string standard;
string version;
string mimeType;
string uri;
string name;
string description;
}
function fetchNFTData(bytes memory nftID) public view returns (IRC27NFT memory irc27NftData) {
require(nftID.length == 32, "NFT ID must be 32 bytes");
bytes32 id = bytes32(nftID);
NFTID nftIDTyped = NFTID.wrap(id);
irc27NftData = ISC.sandbox.getIRC27NFTData(nftIDTyped);
return irc27NftData;
}
function encodeNFTMetadata(
string memory _standard,
string memory _version,
string memory _mimeType,
string memory _uri,
string memory _name,
string memory _description
)
public pure returns (IRC27NFTMetadata memory)
{
string memory json = string(abi.encodePacked(
'{"name":"', _name,
'","description":"', _description,
'","image":"', _uri,
'"}'
));
string memory encodedURI = string(abi.encodePacked(
"data:application/json;base64,",
Base64.encode(bytes(json))
));
function decodeNFTMetadataURI(string memory encodedURI) public pure returns (IRC27NFTMetadata memory) {
// Ensure the encodedURI has the correct prefix
require(bytes(encodedURI).length > 29, "Invalid encoded URI length");
string memory base64JSON = substring(encodedURI, 29, bytes(encodedURI).length);
// Decode the Base64-encoded JSON
bytes memory decodedBytes = Base64.decode(base64JSON);
string memory json = string(decodedBytes);
// Extract fields from the JSON string
string memory name = extractValueFromJSON(json, "name");
string memory description = extractValueFromJSON(json, "description");
string memory uri = extractValueFromJSON(json, "image");
return IRC27NFTMetadata({
standard: "IRC27",
version: "1.0",
mimeType: "application/json",
uri: encodedURI,
name: name,
description: description
});
}
return IRC27NFTMetadata({
standard: _standard,
version: _version,
mimeType: _mimeType,
uri: encodedURI,
name: _name
});
}
}
```
## Example Usage
function substring(string memory str, uint startIndex, uint endIndex) internal pure returns (string memory) {
bytes memory strBytes = bytes(str);
bytes memory result = new bytes(endIndex - startIndex);
for (uint i = startIndex; i < endIndex; i++) {
result[i - startIndex] = strBytes[i];
}
return string(result);
}
To mint a new NFT and fetch its metadata, you would use the above functions within a larger contract that handles NFT minting and management.
function extractValueFromJSON(string memory json, string memory key) internal pure returns (string memory) {
bytes memory jsonBytes = bytes(json);
bytes memory keyBytes = bytes(key);
bytes memory result = new bytes(jsonBytes.length);
uint256 resultIndex = 0;
bool keyFound = false;
for (uint256 i = 0; i < jsonBytes.length; i++) {
if (jsonBytes[i] == keyBytes[0]) {
uint256 j = 0;
while (j < keyBytes.length && jsonBytes[i + j] == keyBytes[j]) {
j++;
}
if (j == keyBytes.length && jsonBytes[i + j] == ':') {
keyFound = true;
i += j + 1;
}
}
if (keyFound && jsonBytes[i] == '"') {
i++;
while (jsonBytes[i] != '"') {
result[resultIndex++] = jsonBytes[i++];
}
break;
}
}
return string(result);
}
}
```

0 comments on commit 0f6293c

Please sign in to comment.