Skip to content

XCM NFT types that use Granular NFT traits#4300

Merged
franciscoaguirre merged 99 commits intoparitytech:masterfrom
UniqueNetwork:feature/asset-ops-traits
Aug 7, 2025
Merged

XCM NFT types that use Granular NFT traits#4300
franciscoaguirre merged 99 commits intoparitytech:masterfrom
UniqueNetwork:feature/asset-ops-traits

Conversation

@mrshiposha
Copy link
Contributor

@mrshiposha mrshiposha commented Apr 25, 2024

Overview

This PR provides new XCM types and tools for building NFT Asset Transactors.
The new types use general and granular NFT traits from #5620.

The new XCM adapters and utility types to work with NFTs can be considered the main deliverable of the XCM NFT proposal. The new types use a more general approach, making integration into any chain with various NFT implementations easier.

For instance, different implementations could use:

  • different ID assignment approaches
    • predefined NFT IDs - pallet-uniques, pallet-nfts
    • derived NFT IDs (NFT IDs are automatically derived from collection IDs) - Unique Network, ORML/Acala, Aventus
  • classless (collection-less) tokens - CoreTime NFTs, Aventus NFT Manager NFTs
  • in-class (in-collection) tokens - Unique Network, pallet-uniques, pallet-nfts, ORML/Acala
  • different approaches to storing associated data on-chain:
    • data is stored entirely separately from tokens - pallet-uniques
    • data is stored partially or entirely within tokens (i.e., burning a token means destroying it with its data) - pallet-nfts (partially), Unique Network, ORML/Acala

With new types, these differences can be abstracted away.

Moreover, the new types provide greater flexibility for supporting derivative NFTs, allowing several possible approaches depending on the given chain's team's goals or restrictions (see the pallet-derivatives crate docs and mock docs).

Also, this is the PR I mentioned in the #4073 issue, as it can be viewed as the solution. In particular, the new adapter (UniqueInstancesAdapter) requires the Update operation with the ChangeOwnerFrom strategy. This brings the attention of both a developer and a reviewer to the ChangeOwnerFrom strategy (meaning that the transfer checks if the asset can be transferred from a given account to another account), both at trait bound and at the call site, without sacrificing the flexibility of the NFT traits.

New types for xcm-builder and xcm-executor

This PR introduces several XCM types.

The UniqueInstancesAdapter is a new TransactAsset adapter that supersedes both NonFungibleAdapter and NonFungiblesAdapter (for reserve-based transfers only, as teleports can't be implemented appropriately without transferring the NFT metadata alongside it; no standard solution exists for that yet. Hopefully, the Felloweship RFC 125 (PR, text) will help with that).

Thanks to the new Matcher types, the new adapter can be used instead of both NonFungibleAdapter and NonFungiblesAdapter:

  • MatchesInstance (a trait)
  • MatchInClassInstances
  • MatchClasslessInstances

The UniqueInstancesAdapter works with existing tokens only. To create new tokens (derivative ones), there is the UniqueInstancesDepositAdapter.

See the pallet-derivatives mock and tests for the usage example.

Superseding the old adapters for pallet-uniques

Here is how the new UniqueInstancesAdapter in Westend Asset Hub replaces the NonFungiblesAdapter:

/// Means for transacting unique assets.
pub type UniquesTransactor = UniqueInstancesAdapter<
	AccountId,
	LocationToAccountId,
	MatchInClassInstances<UniquesConvertedConcreteId>,
	pallet_uniques::asset_ops::Item<Uniques>,
>;

MatchInClassInstances allows us to reuse the already existing UniquesConvertedConcreteId matcher.
The pallet_uniques::asset_ops::Item<Uniques> already implements the needed traits.

So, migrating from the old adapter to the new one regarding runtime config changes is easy.

NOTE: pallet_uniques::asset_ops::Item grants access to the asset operations of NFT items of a given pallet-uniques instance, whereas pallet_uniques::asset_ops::Collection grants access to the collection operations.

Declarative modification of an NFT engine

If an NFT-hosting pallet only implements a transfer operation but not the Stash and Restore, one could declaratively add them using the UniqueInstancesWithStashAccount adapter.

So, you can use it with the UniqueInstancesAdapter as follows:

parameter_types! {
    	pub StashAccountId: AccountId = /* Some Stash Account ID */;
}

type Transactor = UniqueInstancesAdapter<
	AccountId,
	LocationToAccountId,
	Matcher,
	UniqueInstancesWithStashAccount<StashAccountId, NftEngine>,
>;

Supporting derivative NFTs (reserve-based model)

There are several possible scenarios of supporting derivative NFTs (and their collections, if applicable).

A separate NFT-hosting pallet instance could be configured to use XCM AssetId as collection ID and AssetInstance token ID. In that case, the asset transaction doesn't need any special treatment.

However, registering NFT collections might require a special API.
Also, if the NFT-hosting pallet can't be configured to use XCM ID types, we would need to store a mapping between the XCM ID of the original token and the derivative ID.

See the pallet-derivatives crate docs for details.
The example of its usage can be found in its mock and tests.

TODO

No benchmarks were run for pallet-derivatives, so there is no weights.rs for it yet.

@mrshiposha
Copy link
Contributor Author

CC @franciscoaguirre

@mrshiposha
Copy link
Contributor Author

mrshiposha commented Jul 2, 2024

@franciscoaguirre @xlc This PR adds new granular NFT traits to frame-support and provides new XCM types, giving us the instruments to implement XCM NFT in any chain regardless of differences in NFT solutions used in the ecosystem (different pallets, which represent NFTs differently or incompatible with each other, or smart contract-based solutions).

This PR could undoubtedly be divided into at least two. However, I decided to provide all the pieces at once so we can discuss the complete picture. I can divide the PR if needed.

@mrshiposha mrshiposha changed the title Feature/asset ops traits Granular NFT traits and new XCM NFT types Jul 2, 2024
@mrshiposha mrshiposha marked this pull request as ready for review July 2, 2024 17:57
@mrshiposha mrshiposha requested review from a team as code owners July 2, 2024 17:57
// Convert an XCM Location into a local account id:
LocationToAccountId,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
pub type UniquesTransactor = UniqueInstancesAdapter<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change? Is the behavior different than before?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 you added tests to check the behavior is the same

let collection_details =
maybe_collection_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;

// TODO should it be UnknownItem instead of UnknownCollection?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably but changing it is a breaking change

@franciscoaguirre franciscoaguirre added this pull request to the merge queue Aug 7, 2025
Merged via the queue into paritytech:master with commit c1dbcb7 Aug 7, 2025
236 of 238 checks passed
@github-project-automation github-project-automation bot moved this from In-Review to Done in Bridges + XCM Aug 7, 2025
alvicsam pushed a commit that referenced this pull request Oct 17, 2025
## Overview

This PR provides new XCM types and tools for building NFT Asset
Transactors.
The new types use general and granular NFT traits from #5620.

The new XCM adapters and utility types to work with NFTs can be
considered the main deliverable of the **[XCM NFT
proposal](https://polkadot.polkassembly.io/referenda/379)**. The new
types use a more general approach, making integration into any chain
with various NFT implementations easier.

For instance, different implementations could use:
* different ID assignment approaches
    * predefined NFT IDs - pallet-uniques, pallet-nfts
* derived NFT IDs (NFT IDs are automatically derived from collection
IDs) - Unique Network, ORML/Acala, Aventus
* classless (collection-less) tokens - CoreTime NFTs, Aventus NFT
Manager NFTs
* in-class (in-collection) tokens - Unique Network, pallet-uniques,
pallet-nfts, ORML/Acala
* different approaches to storing associated data on-chain:
    * data is stored entirely separately from tokens - pallet-uniques
* data is stored partially or entirely within tokens (i.e., burning a
token means destroying it with its data) - pallet-nfts
([partially](https://github.com/paritytech/polkadot-sdk/blob/8b4cfda7589325d1a34f70b3770ab494a9d4052c/substrate/frame/nfts/src/features/create_delete_item.rs#L240-L241)),
Unique Network, ORML/Acala

With new types, these differences can be abstracted away.

Moreover, the new types provide greater flexibility for supporting
derivative NFTs, allowing several possible approaches depending on the
given chain's team's goals or restrictions (see the `pallet-derivatives`
crate docs and mock docs).

Also, this is the PR I mentioned in the
#4073 issue, as it can
be viewed as the solution. In particular, the new adapter
(`UniqueInstancesAdapter`) requires the `Update` operation with the
`ChangeOwnerFrom` strategy. This brings the attention of both a
developer and a reviewer to the `ChangeOwnerFrom` strategy (meaning that
the transfer checks if the asset can be transferred from a given account
to another account), both at trait bound and at the call site, without
sacrificing the flexibility of the NFT traits.

## New types for xcm-builder and xcm-executor

This PR introduces several XCM types.

The `UniqueInstancesAdapter` is a new `TransactAsset` adapter that
supersedes both `NonFungibleAdapter` and `NonFungiblesAdapter` (for
reserve-based transfers only, as teleports can't be implemented
appropriately without transferring the NFT metadata alongside it; no
standard solution exists for that yet. Hopefully, the Felloweship RFC
125 ([PR](polkadot-fellows/RFCs#125),
[text](https://github.com/polkadot-fellows/RFCs/blob/3a24444278f22dac414fbd2b5c4b205f73d78af7/text/0125-xcm-asset-metadata.md))
will help with that).

Thanks to the new Matcher types, the new adapter can be used instead of
both `NonFungibleAdapter` and `NonFungiblesAdapter`:
* `MatchesInstance` (a trait)
* `MatchInClassInstances`
* `MatchClasslessInstances`

The `UniqueInstancesAdapter` works with existing tokens only. To create
new tokens (derivative ones), there is the
`UniqueInstancesDepositAdapter`.

See the `pallet-derivatives` mock and tests for the usage example.

### Superseding the old adapters for pallet-uniques

Here is how the new `UniqueInstancesAdapter` in Westend Asset Hub
replaces the `NonFungiblesAdapter`:

```rust
/// Means for transacting unique assets.
pub type UniquesTransactor = UniqueInstancesAdapter<
	AccountId,
	LocationToAccountId,
	MatchInClassInstances<UniquesConvertedConcreteId>,
	pallet_uniques::asset_ops::Item<Uniques>,
>;
```

`MatchInClassInstances` allows us to reuse the already existing
`UniquesConvertedConcreteId` matcher.
The `pallet_uniques::asset_ops::Item<Uniques>` already implements the
needed traits.

So, migrating from the old adapter to the new one regarding runtime
config changes is easy.

>NOTE: `pallet_uniques::asset_ops::Item` grants access to the asset
operations of NFT items of a given pallet-uniques instance, whereas
`pallet_uniques::asset_ops::Collection` grants access to the collection
operations.

### Declarative modification of an NFT engine

If an NFT-hosting pallet only implements a transfer operation but not
the `Stash` and `Restore`, one could declaratively add them using the
`UniqueInstancesWithStashAccount` adapter.

So, you can use it with the `UniqueInstancesAdapter` as follows:
```rust
parameter_types! {
    	pub StashAccountId: AccountId = /* Some Stash Account ID */;
}

type Transactor = UniqueInstancesAdapter<
	AccountId,
	LocationToAccountId,
	Matcher,
	UniqueInstancesWithStashAccount<StashAccountId, NftEngine>,
>;
```

### Supporting derivative NFTs (reserve-based model)

There are several possible scenarios of supporting derivative NFTs (and
their collections, if applicable).

A separate NFT-hosting pallet instance could be configured to use XCM
`AssetId` as collection ID and `AssetInstance` token ID. In that case,
the asset transaction doesn't need any special treatment.

However, registering NFT collections might require a special API.
Also, if the NFT-hosting pallet can't be configured to use XCM ID types,
we would need to store a mapping between the XCM ID of the original
token and the derivative ID.

See the `pallet-derivatives` crate docs for details.
The example of its usage can be found in its mock and tests.

#### TODO

No benchmarks were run for `pallet-derivatives`, so there is no
`weights.rs` for it yet.

---------

Co-authored-by: Branislav Kontur <[email protected]>
Co-authored-by: Francisco Aguirre <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T6-XCM This PR/Issue is related to XCM.

Projects

Status: Done
Status: Backlog
Status: Done

Development

Successfully merging this pull request may close these issues.