Basic Usage
Asset Management

Asset-management

MultiAsset module is the simplest one to understand and use, yet extremely powerful. After you have deployed your NFT contract, you can any time expand its functionality by adding new assets. These are the operations you may need to perform:

Writing operations

  1. Adding a new asset.
  2. Adding an asset to a token.
  3. Adding an asset to a token, replacing an old one.
  4. Accepting an asset.
  5. Rejecting an asset.
  6. Rejecting all assets.
  7. Setting priorities.
  8. Approving operators.

The first 3 methods are not part of the standard (ERC-5773) since they are meant to be opinionated. You will not find them in the core implementations, but both our abstract and ready to use implementations include methods to add new assets and add them to tokens. In the two cases, they are gated to the collection owner or contributor. For more details on the implementation levels see the implementation section.

Operations 4 to 8 are part of the standard and are implemented in the core implementations, and consequently in all the higher level ones.

Adding a new asset

In our ready to use implementations, adding a new asset can be performed by the collection owner or a contributor.

The asset is added to the collection and can be later added into any token. Our implementations take care of assigning a unique Id, starting by 1. For more details on the contents of the metadata see the metadata section.

await contract.addAssetEntry('ipfs://metadataURI');

Adding an asset to a token

⚠️

Adding an asset to a token sends it to the pending assets array. It is up to the token owner to accept or reject it.

In our ready to use implementations, adding an asset to a token can be performed by the collection owner or a contributor.

The 3rd parameter is the asset to replace, in this case we are not replacing any asset, so we pass 0.

const tokenId = 1;
const assetId = 1;
const assetToBeReplacedId = 0;
await contract.addAssetToToken(tokenId, assetId, assetToBeReplacedId);

Adding an asset to a token, replacing an old one

In our ready to use implementations, adding an asset to a token can be performed by the collection owner or a contributor.

The 3rd parameter is the asset to replace. If the token does not have such asset, it will simply be added.

const tokenId = 1;
const assetId = 2;
const assetToBeReplacedId = 1;
await contract.addAssetToToken(tokenId, assetId, assetToBeReplacedId);

Accepting an asset

Accepting an asset can be performed by the token owner or an approved party.

⚠️

The replacesId value will be zero if the asset to replace is not in the array of active assets for the token.

⚠️

The index MUST be the index of the asset in the token's pending asset list.

The index parameter is an annoying detail, but it prevents the contract from having to do gas expensive operations, either:

  • Iterate over the list of pending assets to find the index.
  • Keep track of the index for each asset.

We also require the assetId to validate that it is what you expected to be in the index, since another operation could potentially front-run yours.

const tokenId = 1;
const assetId = 1;
const pendingAssets = await contract.getPendingAssets(tokenId);
const index = pendingAssets.indexOf(assetId);
await contract.acceptAsset(tokenId, index, assetId);

Rejecting an asset

Rejecting an asset can be performed by the token owner or an approved party.

⚠️

The index MUST be the index of the asset in the token's pending asset list.

The index parameter is an annoying detail, but it prevents the contract from having to do gas expensive operations, either:

  • Iterate over the list of pending assets to find the index.
  • Keep track of the index for each asset.

We also require the assetId to validate that it is what you expected to be in the index, since another operation could front-run yours.

const tokenId = 1;
const assetId = 1;
const pendingAssets = await contract.getPendingAssets(tokenId);
const index = pendingAssets.indexOf(assetId);
await contract.rejectAsset(tokenId, index, assetId);

Rejecting all assets

Rejecting all assets can be performed by the token owner or an approved party.

Max rejections is the maximum number of assets to reject. If the token has fewer pending assets than max rejections, all pending assets will be rejected. If it has more, it will revert. This is to prevent untentionally rejecting an asset which arrived just before you called the method. The assetId on the AssetRejected event will be 0, meaning that all assets were rejected.

const tokenId = 1;
const pendingAssets = await contract.getPendingAssets(tokenId);
const maxRejections = pendingAssets.length;
await contract.rejectAllAssets(tokenId, maxRejections);

Setting priorities

Setting priorities allows the token owner to signal which of its NFT's assets are more important. For optimization purposes, the priorities must be set 1 to 1 in the same order of the active assets lists.

So if a token has assets [A, B, C], and you set priorities to [2, 1, 3], it means that asset B is the most important, followed by A and C. The length of priorities must match the length of the active assets list or the execution will revert.

When new tokens are added, they are automatically assigned as priority the index they are in the active assets list, so by default every new asset is has a lower priority than the previous ones.

await contract.setPriority(tokenId, priorities);

Approving operators

Approving operators allows the token owner to allow other addresses to perform operations on their behalf. It works exactly as in the ERC-721, but we added specific methods to approve managing assets independently from the token itself.

  • approveForAssets approves an operator to manage a specific asset.
  • setApprovalForAllForAssets allows an operator to manage all the assets of a token.

To remove approvals for single token you call approve with address 0 as the operator. To remove approvals for all tokens you call setApprovalForAll with approved:false.

The operations which an operator can perform are in our implementations are:

  1. Accepting an asset.
  2. Rejecting an asset.
  3. Rejecting all assets.
  4. Setting priorities.
  5. Equipping and unequipping, if Equippable module is used.
const to = '0x...';
const tokenId = 1;
await contract.approveForAssets(to, tokenId);
 
const operator = '0x...';
const approved = true;
await contract.setApprovalForAllForAssets(operator, approved);

Reading operations

  1. Gettting Active assets.
  2. Getting Pending assets.
  3. Getting Asset Metadata URI.
  4. Getting Asset priorities.
  5. Getting Asset replacements

Getting active assets

Used to retrieve IDs of the active assets of given token. To get the metadata of the asset, you need to then call getAssetMetadata(tokenId, assetId).

const tokenId = 1;
const activeAssets = await contract.getActiveAssets(tokenId);
// activeAssets = [1, 2, 3]

Getting pending assets

Used to retrieve IDs of the pending assets of given token. To get the metadata of the asset, you need to then call getAssetMetadata(tokenId, assetId).

const tokenId = 1;
const pendingAssets = await contract.getPendingAssets(tokenId);
// pendingAssets = [5, 6, 7]

Getting asset metadata

Used to retrieve the metadata URI of the asset. Even though our default implementations do not use tokenId to retrieve the metadata, it is required on the standard to allow more advanced implementations, where even the same asset Id could return different metadata depending on the token, for instance by appending the tokenId to get the metadata URI.

const tokenId = 1;
const assetId = 1;
const metadata = await contract.getAssetMetadata(tokenId, assetId);
// metadata = 'ipfs://metadataURI'

Getting asset priorities

Used to retrieve the priorities of the active resources of a given token. The length of this array matches the length of the active assets array, each priority corresponds to the asset in the same index of the active assets array. A lower number means higher priority.

const tokenId = 1;
const priorities = await contract.getActiveAssetPriorities(tokenId);
const activeAssets = await contract.getActiveAssets(tokenId);
// activeAssets = [1, 2, 3]
// priorities = [0, 2, 1]
// This would mean that asset 1 is the most important, followed by 3 and 2.

Getting asset replacements

Used to retrieve the asset that will be replaced if a given asset from the token's pending array is accepted. If the asset is not replacing any other asset, it will return 0.

const tokenId = 1;
const newAssetId = 1;
const replacesAssetWithId = await contract.getAssetReplacements(tokenId, newAssetId);
// replacesAssetWithId = 0. This means that the asset is not replacing any other asset.
// replacesAssetWithId = 2. This means that accepting asset 1 on token 1, would replace asset 2 on the list of active assets.