Skip to main content

Upgradeable NFT (TIP-4.6)

Requires: TIP-4.1 Requires: TIP-6.1

Abstract

This standard describes the operation of upgradeable NFT contracts. This is based on TIP-4.1 and does not describe the functionality proposed there.

The only difference in the minting process is that the Collection deploys an NftPlatform contract rather than an NFT.

Immediately after deployment to the network, the NftPlatform contract calls the tvm.setcode(), tvm.setCurrentCode(), and onCodeUpgrade functions, changing its code to NFT code.

Motivation

The standard allows the NFT code to be changed in case an error is found in it or there is a need to add new functionality.

Specification

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Collection

The NFT collection contract serves as a repository for the most up-to-date version of the NFT code, including its current version number. When an individual NFT contract seeks to upgrade its own codebase, it can initiate a request to the NFT collection contract. Upon receiving this request, the collection contract will automatically update the requesting NFT with the latest version of the code.

Code update functions are not standardized.

interface ITIP4_6Collection {
event UpgradeNftRequested(uint32 oldVersion, uint32 newVersion, address nft, address initiator);
event NftCodeUpdated(uint32 oldVersion, uint32 newVersion, uint256 oldCodeHash, uint256 newCodeHash);

function nftVersion() external view responsible returns (uint32);

function platformCode() external view responsible returns (TvmCell);

function platformCodeInfo() external view responsible returns (uint256 codeHash, uint16 codeDepth);
}

TIP4_6Collection.nftVersion()

function nftVersion() external view responsible returns (uint32);

Returns the current version of the NFT. The Collection stores only the latest version, update history is stored in events.

TIP4_6Collection.platformCode()

function platformCode() external view responsible returns (TvmCell);

Returns the NftPlatform code containing the collection(address) salt. The NftPlatform code is non-upgradeable.

TIP4_6Collection.platformCodeInfo()

function platformCodeInfo() external view responsible returns (uint256 codeHash, uint16 codeDepth);

Returns the hash and depth of the NftPlatform code. These can be used to calculate the NFT address in third-party contracts. The hash is taken from the NftPlatform code containing the collection(address) salt.

Events

event UpgradeNftRequested(uint32 oldVersion, uint32 newVersion, address nft, address initiator);
event NftCodeUpdated(uint32 oldVersion, uint32 newVersion, uint256 oldCodeHash, uint256 newCodeHash);

UpgradeNftRequested

You MUST emit it when an update is requested from the NFT and upgrade is available (the NFT version at the time of the upgrade request is less than the current NFT version in the collection).

NftCodeUpdated

You MUST emit it when NFT code is updated in a collection. The function of code update is not standardized.

Mint

The functions implementing minting are not standardized. The process itself differs from the one proposed in TIP-4.1 (see Abstract).

Upgrade

When NFT code is updated in a collection, the latter changes the NFT version and emits the NftCodeUpdated event.

Nft

interface ITIP4_6Nft {
event NftUpgraded(uint32 oldVersion, uint32 newVersion, address initiator);

function requestUpgrade(address sendGasTo) external;

function version() external view responsible returns (uint32);
}

TIP4_6Nft.requestUpgrade()

function requestUpgrade(address sendGasTo) external;
  • sendGasTo (address) - the address to which the remaining gas will be sent

Calls the Сollection function, checking if there is an upgrade. If a new version is available, the Сollection upgrades the NFT by calling the appropriate function (this function is not included in the standard, implementations may vary).

TIP4_6Nft.version()

function version() external view responsible returns (uint32);

Returns the current version of NFT.

Events

event NftUpgraded(uint32 oldVersion, uint32 newVersion, address initiator);

NftUpgraded

You MUST emit it after the NFT upgrade.

Upgrade

The NFT upgrade scenario is as follows:

  1. The user (in the general case, the NFT manager) calls the NFT requestUpgrade function.
  2. The NFT requests an upgrade from the collection, passing information about itself (id, version).
  3. The Collection compares the version it holds with the one passed by the NFT.
  4. If the version in the collection is larger, then an upgrade is available. The collection emits the UpgradeNftRequested event, calls the NFT function and passes it the new code, version and other necessary data
    1. If the versions are equal, the process aborts and remaining gas is returned to the owner.
  5. NFT sets the new code, emits NftUpgraded event

NftPlatform

NftPlatform's static variables MUST contain _id(uint256) and nothing else.

The code and interface of the contract are not standardized, except for the previously mentioned static _id(static uint256) (similar to the NFT contract). As described above, when minting, the collection deploys this contract instead of the NFT, then it upgrades its code to TIP4_6Nft.

This approach allows the collection to accept messages from NFTs of any version without retaining all the NFT code used.

An example of this contract is shown below. The NftPlatform code is salted with the address of the Collection that deployed it (similar to the NFT contract) so that the codehash is unique for each Collection.

contract NftPlatform {
uint8 constant value_is_empty = 101;
uint8 constant sender_is_not_collection = 102;
uint8 constant value_is_less_than_required = 104;

uint256 static _id;

constructor(TvmCell nftCode, TvmCell data, uint128 remainOnNft) public {
optional(TvmCell) optSalt = tvm.codeSalt(tvm.code());
require(optSalt.hasValue(), value_is_empty);
address collection = optSalt.get().toSlice().decode(address);
require(msg.sender == collection, sender_is_not_collection);
require(remainOnNft != 0, value_is_empty);
require(msg.value > remainOnNft, value_is_less_than_required);

initialize(nftCode, data);
}

function initialize(
TvmCell nftCode,
TvmCell data
) private {
tvm.setcode(nftCode);
tvm.setCurrentCode(nftCode);

onCodeUpgrade(data);
}

function onCodeUpgrade(TvmCell data) private {}
}