Skip to content

Commit

Permalink
improve contracts section for LSP8
Browse files Browse the repository at this point in the history
  • Loading branch information
CJ42 committed Oct 18, 2024
1 parent 3c43da6 commit 4fa57f1
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 252 deletions.
46 changes: 46 additions & 0 deletions docs/contracts/overview/NFT/create-nft-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,49 @@ sidebar_position: 1
---

# Create a Non Fungible Token

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
// modules
import {
LSP8IdentifiableDigitalAsset
} from "@lukso/lsp8-contracts/contracts/LSP8IdentifiableDigitalAsset.sol";
// constants
import {
_LSP8_TOKENID_FORMAT_NUMBER
} from "@lukso/lsp8-contracts/contracts/LSP8Constants.sol";
import {
_LSP4_TOKEN_TYPE_COLLECTION
} from "@lukso/lsp4-contracts/contracts/LSP4Constants.sol";
contract BasicNFTCollection is LSP8IdentifiableDigitalAsset {
constructor(
string memory nftCollectionName,
string memory nftCollectionSymbol,
address contractOwner
)
LSP8IdentifiableDigitalAsset(
nftCollectionName,
nftCollectionSymbol,
contractOwner,
_LSP4_TOKEN_TYPE_COLLECTION,
_LSP8_TOKENID_FORMAT_NUMBER
)
{
// contract logic goes here...
}
}
```

## LSP8 NFT extensions

The `@lukso/lsp8-contracts` package includes token extensions (similarly to OpenZeppelin contracts) that can be added through inheritance. This enables to include specific functionalities for building your token.

| Extension contract | Description |
| :------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
| [`LSP8Burnable.sol`](../contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md) | exposes a public `burn(...)` function that allows any NFT holder or operator to burn a specific NFT tokenId. |
| [`LSP8CappedSupply.sol`](../contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md) | enable to specify a maximum supply on deployment / initialization, which cap the maximum amount of NFT that can be minted in the collection. |
| [`LSP8Enumerable.sol`](../contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md) | functionality to enumerate the list of NFTs in a collection. |
2 changes: 2 additions & 0 deletions docs/contracts/overview/NFT/customise-transfer-behaviour.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ sidebar_position: 2
---

# Customize transfer behaviour

The `LSP8IdenfitiableDigitalAsset` contract implementation includes the `_beforeTokenTransfer` and `_afterTokenTransfer` functions that offer the ability to specify custom logic that can run before or after the token transfer has happen (= before or after the balances in the contract state have been updated).
280 changes: 37 additions & 243 deletions docs/contracts/overview/NFT/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,52 @@ import TabItem from '@theme/TabItem';

# LSP8 Identifiable Digital Asset

The **LSP8 Identifiable Digital Asset** contract is the newest advanced version of the existing ERC NFT standards, such as ERC721.
:::danger Deprecation of `LSP8CompatibleERC721`

The `LSP8CompatibleERC721` contracts have been deprecated and deleted from the [`@lukso/lsp-smart-contracts`](https://github.com/lukso-network/lsp-smart-contracts) package since version `0.15.0`, because of their unsafe nature and [security considerations (See PR #845 for more details)](https://github.com/lukso-network/lsp-smart-contracts/pull/845#issuecomment-1888671461).

LSP8 identifiable digital assets represent **N**on **F**ungible **T**okens (NFTs) that can be uniquely traded.
They are not recommended to be used. However, if you want to still use them, they remain available in the version [`0.14.0`](https://github.com/lukso-network/lsp-smart-contracts/releases/tag/lsp-smart-contracts-v0.14.0).

:::

and given metadata using the **[ERC725Y Standard](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-725.md#erc725y)**.
Each NFT is identified with a tokenId, based on **[ERC721](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol)**.
The **LSP8 Identifiable Digital Asset** contract is the newest advanced version of the existing ERC NFT standards, such as ERC721. LSP8 identifiable digital assets represent **N**on **F**ungible **T**okens (NFTs) that can be uniquely traded. Each NFT is identified with a tokenId, based on **[ERC721](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol)** and can also have its own metadata set using the **[`setDataForTokenId(...)`](../../contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md#setdatafortokenid)** function.

A **bytes32** value is used for tokenId to allow many uses of token identification, including numbers, contract addresses, and hashed values (i.e., serial numbers).

## Comparisons with ERC721
## Installation & Usage

:::danger Deprecation of `LSP8CompatibleERC721`
The LSP8 smart contracts and their ABIs are available are available in their own individual package. To use them, install `@lukso/lsp8-contracts` as a dependency in your project.

The `LSP8CompatibleERC721` contracts have been deprecated and deleted from the [`@lukso/lsp-smart-contracts`](https://github.com/lukso-network/lsp-smart-contracts) package since version `0.15.0`, because of their unsafe nature and [security considerations (See PR #845 for more details)](https://github.com/lukso-network/lsp-smart-contracts/pull/845#issuecomment-1888671461).
<Tabs groupId="provider-lib">
<TabItem value="npm" label="npm" default>

They are not recommended to be used. However, if you want to still use them, they remain available in the version [`0.14.0`](https://github.com/lukso-network/lsp-smart-contracts/releases/tag/lsp-smart-contracts-v0.14.0).
```
npm install @lukso/lsp8-contracts
```

:::
</TabItem>
<TabItem value="yarn" label="yarn" default>

```
yarn add @lukso/lsp8-contracts
```

</TabItem>
<TabItem value="pnpm" label="pnpm" default>

```
pnpm add @lukso/lsp8-contracts
```

</TabItem>
</Tabs>

`LSP8IdentifiableDigitalAsset.sol` is an `abstract` contract that is not deployable as is, because it does not contain any public functions by default to manage token supply (_e.g: no public `mint(...)` or `burn(...)` functions_). You can either:

- the `LSP8Mintable` preset contract that contains a public `mint(...)` function callable only by the contract's owner.
- or extend the `LSP8IdentifiableDigitalAsset` contract (_see below_) and create your own supply mechanism by defining public methods that use the internal `_mint(...)` and `_burn(...)` functions.

## Comparisons with ERC721

<table>
<tr>
Expand Down Expand Up @@ -61,237 +89,3 @@ In comparison ERC721 has:
All of the above functions can be used by both the owner of the token id or by the operator of the token id in order to transfer the ERC721 token. To be mentioned, both functions `safeTransferFrom(...)` have a hook that calls the recipient contract.

Looking at LSP7 & LSP8 we have unified `transfer(...)` & `transferBatch(...)` functions in both contracts. Those functions contain a hook which is executed conditionally and can be used in any of the above cases.

## LSP8 NFT extensions

The `@lukso/lsp8-contracts` package includes token extensions (similarly to OpenZeppelin contracts) that enables to include functionalities for building your token through inheritance.

- [`LSP8Burnable.sol](../contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md)`: exposes a public `burn(...)` function that allows any NFT holder or operator to burn a specific NFT tokenId.
- [`LSP8CappedSupply.sol](../contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md)`: enable to specify a maximum supply on deployment / initialization, which cap the maximum amount of NFT that can be minted in the collection.
- [`LSP8Enumerable.sol](../contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md)`: functionality to enumerate the list of NFTs in a collection.

## Custom logic for transfers

The `LSP8IdenfitiableDigitalAsset` contract implementation includes the `_beforeTokenTransfer` and `_afterTokenTransfer` functions that offer the ability to specify custom logic that can run before or after the token transfer has happen (= before or after the balances in the contract state have been updated).

## Setting metadata for one or multiple tokenIds

The function [`setDataBatchForTokenIds(...)`](../../contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md#setdatabatchfortokenids) can be used to set multiple data key-value pairs at once for one or multiple tokenIds.

This function is flexible enough to enable to set one or multiple [data key-value](/standards/erc725).md#erc725y-generic-data-keyvalue-store) pairs for:

### Case 1: a single tokenId

To set for instance 3 x data key-value pairs for the same `tokenId`, the parameters of `setDataBatchForTokenIds(bytes32[],bytes32[],bytes[])` will be as follow:

<Tabs>

<TabItem value="solidity" label="solidity">

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;
import {
ILSP8IdentifiableDigitalAsset as ILSP8
} from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol";
import {
_LSP4_METADATA_KEY
} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol";
bytes32 constant _NFT_ICON_DATA_KEY = keccak256("NFTIcon");
bytes32 constant _NFT_MARKET_PLACE_URLS__DATA_KEY = keccak256("NFTMarketplaceURLs");
bytes32 constant _TOKEN_ID_TO_SET = 0xcafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe;
function setMultipleDataForSingleTokenId(
ILSP8 lsp8Contract,
bytes memory lsp4MetadataValue,
bytes memory nftIconValue,
bytes memory nftMarketPlaceURLsValue
) {
bytes32[] memory tokenIdsToUpdate = new bytes32[](3);
bytes32[] memory dataKeysToSet = new bytes32[](3);
bytes[] memory dataValuesToSet = new bytes[](3);
// we are setting 3 x data key-value pairs for the same tokenid
tokenIdsToUpdate[0] = _TOKEN_ID_TO_SET;
tokenIdsToUpdate[1] = _TOKEN_ID_TO_SET;
tokenIdsToUpdate[2] = _TOKEN_ID_TO_SET;
dataKeysToSet[0] = _LSP4_METADATA_KEY;
dataKeysToSet[1] = _NFT_ICON_DATA_KEY;
dataKeysToSet[2] = _NFT_MARKET_PLACE_URLS__DATA_KEY;
dataValuesToSet[0] = lsp4MetadataValue;
dataValuesToSet[1] = nftIconValue;
dataValuesToSet[2] = nftMarketPlaceURLsValue;
lsp8Contract.setDataBatchForTokenIds(
tokenIdsToUpdate,
dataKeysToSet,
dataValuesToSet
);
}
```

</TabItem>

<TabItem value="ethers-v6" label="ethers v6">

```js
import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts';

const _NFT_ICON_DATA_KEY = keccak256('NFTIcon');
const _NFT_MARKET_PLACE_URLS__DATA_KEY = keccak256('NFTMarketplaceURLs');

const _TOKEN_ID_TO_SET =
'0xcafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe';

async function setMultipleDataForSingleTokenId(
lsp8Contract,
lsp4MetadataValue,
nftIconValue,
nftMarketPlaceURLsValue,
) {
const tokenIdsToUpdate = [
_TOKEN_ID_TO_SET,
_TOKEN_ID_TO_SET,
_TOKEN_ID_TO_SET,
];

const dataKeysToSet = [
ERC725YDataKeys.LSP4.LSP4Metadata,
_NFT_ICON_DATA_KEY,
_NFT_MARKET_PLACE_URLS__DATA_KEY,
];

const dataValuesToSet = [
lsp4MetadataValue,
nftIconValue,
nftMarketPlaceURLsValue,
];

await lsp8Contract.setDataBatchForTokenIds(
tokenIdsToUpdate,
dataKeysToSet,
dataValuesToSet,
);
}
```

</TabItem>

</Tabs>

### Case 2: different tokenIds

To set for instance the same data key-value pair (_e.g: `LSP4Metadata`_) for 3 x different `tokenId`s, the parameters of `setDataBatchForTokenIds(bytes32[],bytes32[],bytes[])` will be as follow:

<Tabs>

<TabItem value="solidity" label="solidity">

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.12;
import {
ILSP8IdentifiableDigitalAsset as ILSP8
} from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol";
import {
_LSP4_METADATA_KEY
} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol";
bytes32 constant _FIRST_TOKEN_ID_TO_SET = 0xcafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe;
bytes32 constant _SECOND_TOKEN_ID_TO_SET = 0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef;
bytes32 constant _THIRD_TOKEN_ID_TO_SET = 0xf00df00df00df00df00df00df00df00df00df00df00df00df00df00df00df00d;
function setMultipleDataForSingleTokenId(
ILSP8 lsp8Contract,
bytes memory firstTokenIdLsp4MetadataValue,
bytes memory secondTokenIdLsp4MetadataValue,
bytes memory thirdTokenIdLsp4MetadataValue
) {
bytes32[] memory tokenIdsToUpdate = new bytes32[](3);
bytes32[] memory dataKeysToSet = new bytes32[](3);
bytes[] memory dataValuesToSet = new bytes[](3);
tokenIdsToUpdate[0] = _FIRST_TOKEN_ID_TO_SET;
tokenIdsToUpdate[1] = _SECOND_TOKEN_ID_TO_SET;
tokenIdsToUpdate[2] = _THIRD_TOKEN_ID_TO_SET;
// we are setting the metadata for 3 x different tokenIds
dataKeysToSet[0] = _LSP4_METADATA_KEY;
dataKeysToSet[1] = _LSP4_METADATA_KEY;
dataKeysToSet[2] = _LSP4_METADATA_KEY;
dataValuesToSet[0] = firstTokenIdLsp4MetadataValue;
dataValuesToSet[1] = secondTokenIdLsp4MetadataValue;
dataValuesToSet[2] = thirdTokenIdLsp4MetadataValue;
lsp8Contract.setDataBatchForTokenIds(
tokenIdsToUpdate,
dataKeysToSet,
dataValuesToSet
);
}
```

</TabItem>

<TabItem value="ethers-v6" label="ethers v6">

```js
import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts';

const _FIRST_TOKEN_ID_TO_SET =
'0xcafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe';
const _SECOND_TOKEN_ID_TO_SET =
'0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef';
const _THIRD_TOKEN_ID_TO_SET =
'0xf00df00df00df00df00df00df00df00df00df00df00df00df00df00df00df00d';

async function setMultipleDataForSingleTokenId(
lsp8Contract,
firstTokenIdLsp4MetadataValue,
secondTokenIdLsp4MetadataValue,
thirdTokenIdLsp4MetadataValue,
) {
const tokenIdsToUpdate = [
_FIRST_TOKEN_ID_TO_SET,
_SECOND_TOKEN_ID_TO_SET,
_THIRD_TOKEN_ID_TO_SET,
];

const dataKeysToSet = [
ERC725YDataKeys.LSP4.LSP4Metadata,
ERC725YDataKeys.LSP4.LSP4Metadata,
ERC725YDataKeys.LSP4.LSP4Metadata,
];

const dataValuesToSet = [
firstTokenIdLsp4MetadataValue,
secondTokenIdLsp4MetadataValue,
thirdTokenIdLsp4MetadataValue,
];

await lsp8Contract.setDataBatchForTokenIds(
tokenIdsToUpdate,
dataKeysToSet,
dataValuesToSet,
);
}
```

</TabItem>

</Tabs>

## Checking if the Metadata of a tokenId changed

Since LSP8 uses [ERC725Y](/standards/erc725#erc725y-generic-data-keyvalue-store) under the hood, the URI pointing to the metadata of a specific tokenId can be changed inside the ERC725Y storage of the LSP8 contract.

We have seen in the previous section [**how to set metadata for one or multiple tokenIds**](#setting-metadata-for-one-or-multiple-tokenids).

The two functions `setDataForTokenId(...)` and `setDataBatchForTokenIds(...)` emit a [`TokenIdDataChanged`](../contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md#tokeniddatachanged) event. You can listen for this event in the LSP8 contract from your dApp, filtering for the `LSP4Metadata` data key to check if the metadata of a tokenId has been changed. You can do so by filtering the first parameter with the `tokenId` and the second parameter with the [bytes32 value of the `LSP4Metadata` data key](../../standards/tokens/LSP4-Digital-Asset-Metadata.md#lsp4metadata).
Loading

0 comments on commit 4fa57f1

Please sign in to comment.