Cross-chain NFT transfer

Hello everyone!

I am a developer from the Unique Network team. We consider ideas for implementing a cross-chain NFT transfer via XCM v3.

Two central problems should be solved:

  • Parachain trust problem
  • Metadata transferring

The first one could be solved using reserve locations as with fungibles. See Keith Yeung’s answer on StackOverflow for details How can I transfer assets using XCM? - Substrate and Polkadot Stack Exchange. So there is a trusted 3rd party (the reserve location) parachain – the native chain of an NFT.

The other way to solve this is to use SPREE modules so parachains can trust only a SPREE module designed for NFT transferring, but there is no SPREE implementation for now, and it won’t be for a while.

The second problem could be solved via XCM Transact: the NFT metadata could be packed inside an extrinsic argument. There is enough space to store an NFT since the maximum size of an XCM message is 100 kb.

So, here is our current idea of cross-chain NFT transferring:

  • Chain A holds an NFT originating at chain S (this NFT is native to chain S)
    So, chain A possesses the NFT at chain S by its sovereign account, and chain A provides a derivative of the NFT.
  • If the NFT owner wants to transfer it to another chain B, chain A will:
    • Lock (not burn) the NFT on its side (InitiateReserveWithdraw)
    • Ask chain S to transfer the NFT from chain A’s sovereign account to chain B’s sovereign account (DepositReserveAsset)
    • DepositReserveAsset to chain A’s sovereign account on chain B (we are not ready to give the NFT to the target account since the metadata can’t be transferred via XCM Transact now – the origin is cleared due to reserve transfer) and report back about the result of the program (XCM ReportError).
    • [error] If there is an error, chain A automatically sends the ClaimAsset command and initiates reserve transfer to the original owner on chain A.
    • [ok] If the deposit asset command succeeds, we can transfer the metadata via XCM Transact. We expect it to succeed (ExpectTransactStatus). If there is any error – initiate a reserve transfer to the original owner on chain A. Report about the outcome of the XCM program (XCM ReportError)
    • [ok] If XCM Transact succeeded (chain A receives the empty error code after the execution), chain A burns the locked NFT on its side.

If chain A doesn’t burn/lock a sent NFT (hence creating an NFT duplicate), the information from the reserve location should be used to resolve a dispute on what it is an actual instance of the NFT.

Suppose we just want to send an NFT from chain S (the native location for an NFT) to another chain. In that case, we will just place it under the corresponding sovereign account and execute XCM Transact to transfer the metadata (so the source and the reserve locations are the same).

It may make sense to develop a standard pallet for metadata transferring to simplify the process.

But still, there is at least one major issue – chain A could run out of money in its sovereign account on chain B or chain S to perform rescue actions. As a result, it would not automatically initiate reserve transfer in the backward direction in case of issues, creating a possible big maintenance problem.

Here is the possible XCM code for the described idea:

  1. Chain A → Chain B (deposit the NFT to A’s sovereign account on chain B)
    Cross-chain NFT transfer using XCM v3 · GitHub
  2. Chain A claims the asset back in case of the DepositAsset error.
    Cross-chain NFT transfer using XCM v3 · GitHub
  3. Chain A issues XCM Transact to transfer NFT metadata and deposits it to the target owner
    Cross-chain NFT transfer using XCM v3 · GitHub

Does anyone have any suggestions/thoughts about this? Is there a better solution?

Thanks!

4 Likes

w.r.t. to the issue that Chain A may run out of funds to execute XCM for recovery actions, is it not reasonable for the initiator of the sending protocol to provide sufficient funds to Chain A’s sovereign account? The actual acquisition of tokens could be abstracted, but it doesn’t seem sustainable for Chain A to cover these operations on behalf of users.

Yes, it seems the initiator should fund the sovereign account somehow. However, maybe I am missing something, but I can’t see a robust way to do this:

  • What if these funds will be used to rescue another NFT?
  • How to ensure sufficient funding?
  • This operation should be as seamless as possible, but how to combine the sovereign account funding and NFT transferring/rescuing (I mean, do it all automatically so that the user should sign only one transaction. Maybe through some pallet that does the stuff)?

As far as I can see, we can’t use a reserve location for this (since we should already have some funds in the sovereign account).

Up to this point, the steps look OK to me.

This step here starts to not make any sense. DepositReserveAsset is intended to only be executed by reserve chains, so why would chain B suddenly act as a reserve for chain A?

Moreover, in the previous step, chain S would have already transferred the actual NFT to chain B’s sovereign account on S, and a ReserveAssetDeposited message would have been sent to chain B as a result. There is no need for chain A to send another message to chain B telling B to perform any sort of action – chain B can simply act on ReserveAssetDeposited to mint a derivative NFT.

This step here seems to be the most important one, since the rest of the procedure is based on whether this step succeeds or fails, so I am really not sure what the purpose this step is serving.

This step aims to give chain A the right to execute XCM Transact to transfer the NFT’s metadata automatically. So, chain A receives the status of the DepositAsset execution on chain B and can send the metadata. Furthermore, it is intended to make the transfer process of NFT+metadata smoother: the sender user should only initiate the transfer, and the chain will perform the rest of the actions.
The NFT is stored on chain B under the sovereign account of chain A for a short amount of time (Unfortunately, I didn’t explicitly mention that in the original post); after transferring the metadata, the NFT should be deposited to the target user account as shown in code here: Cross-chain NFT transfer using XCM v3 · GitHub

This complexity isn’t needed for fungibles since fungibles don’t contain any associated information. However, when discussing NFTs, we should decide how to handle the metadata during cross-chain transfer since there is no point in transferring an empty NFT (just an id without associated information).

We could:

  1. Let the user set the new metadata on the target chain
  2. Let the sender chain set the metadata on the target chain when the NFT is deposited
  3. Handle the metadata some other way…

I suggest going with the 2nd option because we could do all the stuff automatically (i.e., the user initiates the cross-chain transferring, and the chain does all the necessary actions) so that the NFT metadata will be set according to the original NFT state.

But it is undoubtedly not a pretty solution; I would be glad if a better alternative exists.

I’m wondering about the collections on different chains mapping.
Since each NFT belongs to some collection, the user needs to be sure that NFT will arrive at the proper collection on another chain.
So, how could we setup that mapping and who will be the owner of the derived collection on another chain?

Why is this necessary? The actual NFT is stored on chain S, I don’t see why or how chain B could store an NFT that belongs to chain A?

The thing is: the metadata of an NFT is mutable. So we have to transfer the metadata to the target chain so that it will have the full state of the NFT, which can be mutated by chain B in the future. It is crucial to set the correct metadata since all the NFT implementations in DotSama use some notion of metadata: properties or attributes (Unique Network, Both v1 and v2 versions of Uniques pallet by Parity, RMRK, ORML nfts). There are slight differences in approaches, but overall the idea of information associated with an NFT is common. And it is the thing that makes NFTs useful.

As I wrote in the previous reply, offloading the work (metadata transferring) to Chain A seems reasonable. That is why Chain A temporarily owns the NFT by its sovereign account on Chain B so that it may execute XCM Transact to send the metadata and then “release” the NFT to the target account on chain B. But the XCM Transact could fail, so we should return the NFT to the original owner in that case. Since Chain A owns the NFT, it can transfer it back to the original NFT owner via the regular InitiateReserveWithdraw command. If Chain A is not an owner of the NFT when metadata transferring fails, then some special mechanism is required to transfer the NFT back (as it seems to me, at least).

So, how could we setup that mapping and who will be the owner of the derived collection on another chain?

In my view, it is up to the target chain. Like with the fungibles, each chain has to figure out how to handle the foreign assets (set up some asset registry or something). Or maybe existing native collections could “host” the foreign NFTs, and the mapping “foreign NFT id → native NFT id” could be stored somewhere else. For example, the owner of a collection could allow only specific foreign NFTs to be hosted inside the collection.

I don’t see the connection with your previous point here. Your previous point was:

Which I agree, but I don’t see why that necessarily implies that chain A suddenly sees chain B as a reserve for an NFT that originally belonged to chain S. Recall that the existence of a sovereign account on a chain means it is acting as a reserve chain, i.e. chain B now acts as a reserve for a derivative NFT on chain A, even if it is temporary.

If the conversation is framed this way, then it should be obvious that we want to have only 1 reserve holding the actual NFT, which is chain S. Chain B shouldn’t be acting as a reserve for chain A, and definitely not over a derivative NFT either. What should happen then is for chain A to transfer metadata to chain S, and then have chain S transfer metadata to chain B as part of the reserve-backed transfer mechanism.

In code, it would look something like this:

vec![
    InitiateReserveWithdraw {
        // ...
        xcm: vec![
            BuyExecution { .. },
            Transact { .. },              // <---THIS INSTRUCTION IS ADDED
            DepositReserveAsset {
                // ...
                xcm: vec![
                    SetAppendix(vec![
                        ReportError { .. },
                    ].into()),
                    Transact { .. },      // <---THIS INSTRUCTION IS ADDED
                    DepositAsset { .. },
                ].into(),
            }
        ].into(),
    }
]

However, this does sound like a tedious operation, so I have another idea in mind. My opinion is that since we know that chain S owns the NFT, it should then make sense to have all metadata stored on the NFT on chain S. The derivative NFTs should then not worry about modifying or moving metadata around when transferring NFTs. When a metadata update is required, an XCM would then be sent from chain A to chain S to make such an update. Is this viable? I am suspecting that this was not thought of before due to the latency issues in transferring XCMs, but I lack enough context to say this definitively.

When a metadata update is required, an XCM would then be sent from chain A to chain S to make such an update. Is this viable?

In the majority of use cases (probably 90% or more), chain S would never accept the metadata changes from any other chain. All because the metadata is the main thing that determines NFT’s value.

If this is the case, then we definitely are trying to shoehorn cross-chain NFT transfers to a model that doesn’t work for it. Having a reserve chain means that all the actual value is stored within that chain; all other chains would only be interacting with derivatives of the reserve asset.

What you’re saying here is that over time, the derivatives are able to obtain value on their own, independent and divorced from the value of the reserve asset. If there is no mechanism in updating the metadata for the reserve asset, we can thus conclude that the reserve asset is no longer fit for being a reserve, and so the reserve-backed asset transfer model does not fit this use case.

Recall that the existence of a sovereign account on a chain means it is acting as a reserve chain, i.e. chain B now acts as a reserve for a derivative NFT on chain A, even if it is temporary.

Oh, I didn’t consider it this way. I thought we viewed only chain S as the reserve location since it is the native chain for the NFT. And when the NFT is temporarily placed under chain A’s sovereign account on chain B, it means temporary ownership only for two purposes: execute the XCM Transact and, in case of an error, return the NFT.

However, it seems my view was incorrect.

Anyway, here is why I used this “temporary chain A ownership” in the beginning:

  1. To be able to execute XCM Transact with chain A origin
  2. To give chain A the ability to transfer the NFT back on its own

As for the 1st point: if we could supply XCM Transact inside the ‘xcm’ field of the InitiateReserveWithdraw / DepositReserveAsset commands, we wouldn’t need the temporary ownership. But it is not the case. To execute XCM Transact, the Origin register must contain a (correct) value; otherwise, we will get the BadOrigin XCM error: https://github.com/paritytech/polkadot/blob/54bcee1d5615f53781d5172fcce6700dbf35dad2/xcm/xcm-executor/src/lib.rs#L544-L546. But both InitiateReserveWithdraw and DepositReserveAsset commands clear the Origin register:

Thus, we cannot supply XCM Transact via the InitiateReserveWithdraw / DepositReserveAsset commands.

However, if a sovereign account owning an asset always means the hosting chain (chain B in our case) is the reserve location, the initial idea won’t work. Hence, we have to figure out some other means to make the cross-chain NFT transfer possible…

As for the idea of updating metadata on chain S: even though different NFT implementations have a more or less common notion of metadata, the update approaches are entirely different:

  • On Unique Network, there are different access rights:
    • A mutable flag – can the property be updated after setting the initial value?
    • A collection owner flag – can the collection owner update the property?
    • An item owner flag – can the item owner update the property?
  • The nfts pallet by Parity (a.k.a Uniques v2) has namespacing – a collection owner, an item owner, approved users, and the blockchain itself – all have different namespaces to write to.
  • RMRK allows only the collection owner to set the properties and only when the collection owner holds the NFT in question.

So, only the hosting chain can decide whether it is legit to update the metadata or not. The reserve chain can’t know about the rules of another chain.

If there is no mechanism in updating the metadata for the reserve asset, we can thus conclude that the reserve asset is no longer fit for being a reserve, and so the reserve-backed asset transfer model does not fit this use case.

In light of all the above, it seems there is currently no way to implement the cross-chain NFT transfer except by using XCM teleports. This implies mutual trust between parachains and will likely be very difficult to maintain with an increase in the number of participating parachains.

Thus the crucial question arises: is there any other way to implement the cross-chain NFT transfer with the existing Polkadot tools?

I have an idea of how to implement it using SPREE. But, alas, there is no SPREE implementation right now and no concrete information about its interface/capabilities, so this idea is based on my speculation and assumptions after reading several articles and documents about SPREE. That is why the SPREE idea isn’t viable until SPREE arrives. And we don’t know when will it happen – and maybe the final design of SPREE wouldn’t help us to implement NFT transferring in the end.

1 Like

Apologies for misleading you – the existence of an sovereign account does not imply that the chain is acting as a reserve. I’ve mistaken the logic behind it, i.e. reserve chain implies sovereign account, but sovereign account does not imply reserve chain; the converse is not necessarily true.

As such, the code as you have written works, but let me explain further why I initially felt that the entire process is flawed – it’s due to the fact that chain A now needs to trust chain B for safeguarding its sovereign account. The entire idea behind reserve-backed asset transfers is that A and B don’t necessarily trust each other but both trust chain R, and hence they can communicate via R. This additional step of transferring NFTs to the sovereign account of A on chain B now re-establishes the requirement of trust between A and B.

Thus, my previous idea was to use a 2-step process: have chain A transfer the metadata of the NFT back to the reserve NFT on chain R first, and have chain R then transfer the metadata to the derivative NFT in chain B. It is indeed a tedious process, but this ensures that the trust barriers are not breached.

As such, the code as you have written works, but let me explain further why I initially felt that the entire process is flawed – it’s due to the fact that chain A now needs to trust chain B for safeguarding its sovereign account. The entire idea behind reserve-backed asset transfers is that A and B don’t necessarily trust each other but both trust chain R, and hence they can communicate via R. This additional step of transferring NFTs to the sovereign account of A on chain B now re-establishes the requirement of trust between A and B.

Thank you for highlighting this for me! I totally agree with this.

Considering this and several other issues that came to me, I had to rework the solution.

There are issues with using the XCM Transact as the utility to transfer metadata. The limit of 100 kb is not limiting a single XCM message but the entire HRMP channel. So if there are many cross-chain NFT transfers in a short time, many of these transfers will be rejected since they won’t fit into the channel. Thus, we have to optimize the metadata transfer. Since we don’t have the means to transfer big chunks of data via XCM, we should use an on-chain-verifiable but off-chain method.

Abstract

The main idea of the new approach is to use the SetTopic XCM command to transfer the short, fixed-length hash of the metadata alongside an NFT. Accordingly, the NonFungibleAdapter should react to the set topic. The metadata will be provided off-chain via the Preimages pallet. The metadata hash represents the valid metadata so the target chain can check the correctness of metadata provided off-chain. This approach demands a user to take more than one action to perform the cross-chain transfer, but in return, the method is more straightforward, which means it is less error-prone.

Why is the metadata hash sufficient? The previously discussed method struggles to send authentic NFT metadata from the sender chain. It tries to send the actual data through XCM, but we only care that the target chain receives the metadata identical to the last metadata state on the sender chain. There is no need to make the Reserve Chain hold the metadata for this purpose; we only need the sender chain to send the metadata hash representing the correct metadata. Since the sender chain is the owner of the NFT (and the Reserve Chain testifies to it), we trust this hash.

The other thing to mention is the metadata format. While different chains have different metadata formats, it would be good to have some standard formats to communicate between chains. There could be several standards, so we have to provide some metadata container for transfers describing:

  • The standard used
  • Maybe some information about the standard
  • The payload itself – the format defined by the standard, but one way or the other, it will be some key-value pair set

The metadata hash should be computed based on this metadata container – the hash will represent not only the payload but the standard of the metadata to maintain metadata format flexibility.

Simplified description

There are too many things to describe to explain the entire implementation, despite the more straightforward nature of the new solution. So let’s define the new approach without complicating details to avoid losing the point.

Cross-chain NFT transfer flow (the happy path)

Birds-eye view

From a user perspective, five actions should be performed by them to transfer an NFT:

  1. Preparing the NFT for transferring – an extrinsic call

  2. Building and executing the XCM program – an extrinsic call of some pallet

  3. Acquiring the metadata from the source chain via the Runtime API

  4. Noting the metadata Preimage on the target chain

  5. Binding the metadata to the NFT – an extrinsic call that will interpret the metadata blob from the Preimage pallet and store the metadata in the native representation

Detailed view
  • The Sender wants to send an NFT to the Beneficiary

    • The Sender calls the “prepare-for-xc-transfer” extrinsic. This extrinsic will:
      • Compute the metadata hash of the NFT. We can’t do it during the transfer because this operation could be unpredictably costly – remember, it computes the hash of the metadata container, so the chain should translate the metadata from its internal representation to the standard one and then compute the hash (during the sending of the XCM message, the chain will take this hash and pass it to the SetTopic command)
      • Lock the NFT for the non-XCM operations – since we computed and stored the hash, the NFT shouldn’t be modified
      • Remember the current owner of the NFT. This is needed for error handling (will be described later)
  • The Sender sends the NFT with the help of a special “xcm-nft-transfer” pallet (actually, it could be the “xtokens” pallet). The pallet executes the usual InitiateReserveWithdraw XCM command and supplies the hash of the current metadata along with other XCM commands. The pallet utilizes the SetTopic XCM command for this purpose

  • The target chain receives the XCM commands, and the NonFungibleAdapter should take into account the current XCM context (the following could be abstracted in Substrate):

    • It should read the topic – it’s the hash of the metadata
    • Request the preimage
    • Check if the metadata preimage exists
    • [maybe] If no such data is provided, emit an event informing the user (or some other interested party) that the metadata should be provided to make the NFT usable on the target chain
    • Accept the NFT but keep it locked for non-XCM operations since we can’t bind the metadata yet because it’s hard to predict how much weight it would take (we don’t know the weight of the metadata blob interpretation and subsequent storing it in the target chain’s NFT storage)
  • Someone (this operation can be done by anyone) acquires the current metadata by reading it on the source chain via the Runtime API

  • Someone notes the preimage on the target chain with the acquired metadata. The Preimages pallet generates a hash for the metadata. (Note that if the hash of the provided data is correct, this operation will be free after the transfer because the target chain requested this preimage. See the Preimage pallet code: https://github.com/paritytech/substrate/blob/897b95d98424a44eb493eb982e5ac88d37f60cb5/frame/preimage/src/lib.rs#L151-L167)

  • Once the NFT is deposited and the metadata preimage is noted, Someone (this operation is a common good) can bind the metadata to the NFT via an extrinsic. This extrinsic will try to interpret the metadata blob, translate it into the chain’s internal representation, and store it as native metadata. In case of success, the extrinsic will unrequest the metadata blob provided by its hash, hence removing the metadata preimage. Also, it will unlock the NFT for non-XCM operations.

Benefits

Relaxed NFT assets limit per cross-chain transfer

We can transfer a lot of NFTs without worrying if we can fit them into the HRMP channel since we are transferring only the NFT ids and the 32-byte metadata hashes.

Addressing the cross-chain metadata compatibility issue

Let’s describe the core issue we are considering in this analysis, which could occur when using the Reserve Chain concept and show how the alternative approach proposed above would solve it.

Each parachain that handles NFTs has its own set of rules for metadata. These rules dictate which keys are available and which of them can or cannot be updated. If an NFT is transferred with incompatible metadata, it will not be possible to bind it to the NFT on the target chain. However, this is a logical limitation, not a physical one. Even if the NFT metadata needs to be adjusted before or after the transfer, the owner may still want to transfer the NFT nonetheless.

  • The adjustment of the NFT could be made manually by the NFT owner before the transfer – the target chain could provide a Runtime API allowing the NFT owner to perform a compatibility check prior to executing a transfer.
  • The adjustment of the NFT could be made automatically by the target chain when binding the metadata. But the owner’s explicit consent is required to perform the metadata coercion.

In either scenario mentioned above, the metadata may be lost or altered - a lossy conversion. As a result, the NFT owner should be aware of what will happen and either make the adjustments manually or give explicit consent for the adjustments to be made.

It is not a big problem considering the 2-party transfer: Reserve Chain → Derivative Chain, and vice versa.

But, if we consider the 3-party transfers (Chain A → Reserve Chain → Chain B), it becomes obvious that we could easily induce unnecessary data loss during the transmission.

Chain A(possible data loss)Reserve Chain(possible data loss)Chain B.

But here is the question: if the Reserve Chain has no other role but to safeguard the NFT (meaning, the NFT will not participate in the Reserve Chain’s logic and hence has no obligation to comply with its rules regarding metadata), why is an adjustment step that could cause data loss being considered in the above procedure at all?

A way to sidestep this problem entirely is: the Reserve Chain shouldn’t hold the metadata for these transfers! The only thing that it must do is to transfer the NFT between sovereign accounts and send the metadata hash to the target chain. Since the metadata hash was provided by Chain A while it held the NFT under its sovereign account, this hash represents the final state of the NFT provided by its owner – Chain A. There is no need to store the metadata anywhere since it is already stored in the previous blocks on Chain A. The hash guarantees that only the correct metadata will be bound to the NFT, so we don’t have to trust Chain A anymore.

Actually, the origin of the metadata becomes irrelevant as the integrity of the NFT is maintained by the hash.

Note: it is not mandatory that two different parachains have different metadata rules. They may be compatible, but we must describe the general case here.

The NFT metadata format is flexible

Using an abstract metadata container for the NFT transferring doesn’t limit the chains’ understanding of the metadata. Several different standards (and possibly different versions of them) could be defined in the future.

Not only parachains

Since this approach uses the standard XCM operations only to ensure the authenticity of the metadata while at the same time the metadata itself is transferred off-chain – NFTs can be transferred not only between parachains but also to/from the external consensus systems like Ethereum via the corresponding bridge.

Error handling

We should avoid Governance involvement as much as possible when something goes wrong. For example, if the DepositAsset XCM command fails, the error reporting should automatically trigger the NFT rescue/recovery procedure. Recall that each parachain decides on its own how to handle foreign NFTs. It can also reject an NFT, so we must ensure that the NFT can never be lost.

The target chain should notify the sender chain (either through the Reserve Chain if it is a 3-party transfer or directly if it is a 2-party transfer) when an XCM error occurs. We can do that using the ReportHolding XCM command in the XCM Error Handler. Upon receiving the Response::Assets message (with the dedicated query id for error handling of the XC-NFT transfers), the sender chain will need to restore all the NFTs mentioned in the message to the original owners and request the metadata hashes for future metadata binding (which means the chain needs to keep track of the original owner and the corresponding hash during the cross-chain transfer preparation stage).

Restoring the NFT metadata is identical to its transferring – a user should acquire the metadata from the previous blocks via Runtime API and then bind it to the NFT.

However, some errors (e.g., when insufficient funds were supplied to BuyExecution, resulting in an Asset Trap scenario) cannot be handled without Governance. But such errors would need to be caught during testing.

If the error message goes through the Reserve Chain (a 3-party transfer), the Reserve Chain should transfer the NFTs back to the sender chain’s sovereign account and send a similar error message to the sender chain so it can restore the NFTs on its side.

Worthy of mention

  • The same algorithm should compute the metadata hash on both sides. But the Preimage pallet’s hashing algorithm could differ on the target chain. If so, the second instance of the Preimage pallet with the correct hashing algorithm should be used on the target chain.
  • Recall the problem of funding the sovereign accounts from the previous solution. Transferring NFTs is not so tricky with the new approach and perfectly fits the XCM design. Note that when parachains open HRMP channels, they usually have means to transfer their native tokens between them. With the new approach, we can easily transfer native tokens to buy execution on the target chain alongside NFTs.

Implementation

This solution can be easily integrated into the current infrastructure.

  • The standard NFT interface (from “frame_support”) should be able to lock NFTs for non-XCM operations.
  • The NonFungibleAdapter should use the extended NFT interface to lock the NFT for non-XCM operations. Also, the adapter should consider the XCM context.
  • The “xtokens” pallet can be extended to work with NFTs and their metadata (it should compose the correct XCM programs and respond to the errors/successes accordingly).
  • Currently, the Polkadot XCM pallet can process only one query response per query id. Therefore, it would be good to preallocate a couple of query ids for multiple error/success handling.

Here you can find the sequence diagrams and XCM programs’ pseudocode: XC-NFT transfer + preimages · GitHub.

I would be happy to improve this solution if I missed something.

Moreover, if this solution seems viable, the Unique Network team is ready to implement all the necessary parts to give the ecosystem the means for cross-chain NFT transferring.

3 Likes

I like your idea with the pre-image pallet; it really simplifies the flow!

1 Like

To be fair, Keith mentioned the Preimage pallet for this purpose some time ago in a personal conversation. Initially, I thought there was no way to guarantee metadata authenticity and NFT rescuing. But it turns out there is a way :slight_smile:

Thanks a lot @mrshiposha for your hard work to standardize the NFT transfers through XCM :slightly_smiling_face:

I’m one of the core developers of Moonbeam and I’m particularly interested in this topic because I’ll most probably be implementing ERC721 NFTS transfers through XCM in the coming months.

I would be happy to help contribute to the xtokens pallet, or a new “xnfts” pallet on the ORML repository (our team is already contributing to this repo).

I like the idea of specifying a generic format that can support several standards, I guess ERC721 will be one of them.
I think it would be necessary that these specifications are written somewhere, for example in a git repository as it is already the case for xcm.
Do you think we can use xcm-format? or ORML? Or would a dedicated “community standards” repository be more relevant? Or do you have another idea?

I think that such specifications should not talk about the pre-image pallet, but only define the format of structured NFT metadata and how to hash these data.

Each parachain, or other consensus system, wishing to follow these specifications must remain free as to how to allow users to inject the metadata, good specifications are limited to the minimum necessary to agree on.

Concerning the modification of the metadata of an NFT, some standards like ERC721 don’t specify how to modify the metadata on-chain.
Some smart contracts may allow it (and a remote chain could then modify such an NFT on Moonbeam thanks to our “XCM to EVM” feature), but this is not foreseen in the ERC721 standard, and it’s a voluntary design.

It is up to those who wish to create other specifications for NFT metadata mutations, but I think that we should have specifications limited to how to transfer an NFT and a proof of it’s metadata content.

Also, to remain consistent with the “reserve-based model” and its guarantees, I think it is up to the reserve chain to transfer the hash of the metadata to the destination chain.

If the sender chain has modified the metadata, it can transmit the new hash to the reserve chain, but the reserve chain must remain free to refuse these changes, in any case the NFT transfer specifications cannot require the reserve chain to accept the metadata change, otherwise it will be impossible to be compatible with some standards like ERC721.

We still have the choice between 3 different solutions:

  1. Do not include the new metadata hash in the message sent to the reserve chain, If the sending chain modifies the metadata in a way that is supposed to be acceptable to the reserve chain, it must inform the reserve chain prior to the transfer according to other specifications for metadata mutation.
  2. We allow sending a new hash to the reserve chain in the same message as the transfer, but in this case the reserve chain must have the possibility to ignore or refuse this new hash. There are then two possibilities:
    1. a) If the reserve chain refuses the new hash, it simply ignores it and sends the old hash to the destination chain.
    1. b) If the reserve chain refuses the new hash, it generates an error and the NFT is not transferred.

I think the most correct and secure solution is 2b.

This is my first feedback, maybe I misunderstood some things or missed some key notions, in this case I would be happy to be enlightened :slight_smile:

Well, I’m excited to help write/code/review the specifications and implementation, depending on the time I could give :smiley:

1 Like

Thank you for your feedback @librelois!

I think it would be necessary that these specifications are written somewhere, for example in a git repository as it is already the case for xcm 2.

I totally agree with this! We need a place for the community XC-NFT standard. We should have a dedicated repository since that repo could accumulate many things regarding the XC-NFTs, so I think we shouldn’t place it under less specific ones like XCM format or ORML.

I propose using this repository for this purpose.

I can provide the initial setup for the repo, and we can evolve it onwards :slightly_smiling_face:

I think that such specifications should not talk about the pre-image pallet, but only define the format of structured NFT metadata and how to hash these data.

Each parachain, or other consensus system, wishing to follow these specifications must remain free as to how to allow users to inject the metadata, good specifications are limited to the minimum necessary to agree on.

Yes, using a pre-image pallet is not mandatory; I wanted to describe a possible solution using the existing tools from the beginning to the end. The only things that matter are the following:

  • Structure of the XCM programs – SetTopic with the metadata hash specified before each DepositAsset command for an NFT
  • Locking an NFT for non-XCM operations on the needed occasions
  • Metadata formats
  • Hashing algorithm

How the target chain acquires the actual metadata and verifies it by the hash is irrelevant. However, using the pre-image pallet could be a recommendation (because it is a Substrate-way for storing data blobs and the free loading of the metadata requested by the target chain).

a remote chain could then modify such an NFT on Moonbeam thanks to our “XCM to EVM” feature

Honestly, at the moment, I don’t possess enough information about Moonbeam’s internal structure, but let me share my thoughts on how I think transfers should work in general. When we transferred the NFT from the Reserve Chain to another chain A, the Reserve Chain could safely remove all the metadata on its side (a user still can acquire the metadata for transferring via the Runtime API from the Reserve Chain’s previous blocks) because now chain A is the owner of the NFT and, as a sovereign entity, can do whatever it wants. The Reserve Chain doesn’t need to store or update the metadata on its side since chain A is now fully responsible for the NFT future story until the NFT is transferred to another chain. The only thing that the Reserve Chain must do is to safeguard the NFT for chain A.

I think we can substitute “the Reserve Chain” with “Moonbeam” in the above.
In the proposal, I described the NFT transferring between chains but left the internal chain’s details untouched (mostly :slightly_smiling_face:). Including how the target chain could transfer the NFT into its internal consensus entities (such as EVM contracts) – I think it is up to the chain. Yes, maybe we need a standard for the NFTs transferring into EVM contracts (since many parties would be interested). Still, I suggest we go step by step and start with just a cross-chain NFT transfer leaving the dealing with the Smart Contracts to the target chains. I think it’s highly likely that Moonbeam’s solution in this matter would become the standard.

Also, to remain consistent with the “reserve-based model” and its guarantees, I think it is up to the reserve chain to transfer the hash of the metadata to the destination chain.

If the sender chain has modified the metadata, it can transmit the new hash to the reserve chain, but the reserve chain must remain free to refuse these changes, in any case the NFT transfer specifications cannot require the reserve chain to accept the metadata change, otherwise it will be impossible to be compatible with some standards like ERC721.

I believe that it would be very prohibitive for the derivative chains. Yes, technically, it’s possible – the NonFungibleAdapter of the Reserve Chain can examine the XCM Context Topic and decide to return an error during the DepositAsset execution. However, why would the Reserve Chain impose rules on how the NFTs are modified on the derivative chains? A derivative chain is the rightful owner of a given NFT and has the right to do with the NFT whatever it prefers and needs to. Yes, when the NFT is transferred from that derivative chain to another chain (including the Reserve one), the NFT could be incompatible with the destination chain. But this is not a problem since after the transfer (but before binding the metadata), the NFT will be locked for non-XCM operations (only for non-XCM operations, such as metadata update or any other chain’s logic), meaning that the NFT can still be transferred further despite its incompatibility with this particular chain. Or the incompatible metadata can be coerced to the format supported by the chain (as I wrote about this in the “addressing the cross-chain metadata compatibility issue” section).

Note that binding the metadata is the only way to finally “land” the NFT on the target chain (the NFT will be unlocked for non-XCM operations). The “bind-metadata” extrinsic could have custom checks provided by the chain. For example, if the metadata is incompatible – the chain can throw a metadata incompatibility error and refuse to “land” this NFT.

When a user transfers an NFT to another chain, they may want not only to transfer just an NFT but also to give the NFT capabilities not provided by the source chain. It means an NFT shouldn’t be obligated to preserve the standard (like ERC721). The NFT could have a different standard (or the old one + some extension) on the target chain. I believe that we shouldn’t restrict chains (since they are sovereign entities and they possibly offer unique capabilities for NFTs) in their actions regarding NFTs. They will continue the story of a given NFT in their own unique way.

We need to preserve the NFT’s continuous history, meaning that we don’t interrupt the NFT’s evolution during the XC-transfer: that is why we want to send the correct metadata alongside the NFT.

The target chain should know how to interpret the metadata, so we need the metadata standards; As for the ERC721 Metadata, it could be just the following:


metadataContainer = {
    “standard”: “ERC721-Metadata”,
    “version”: “1”,
    “payload”: {
        "tokenURI": {
            "type": "string"
        }
    }
}

Please, correct me if I misunderstood your message or missed something :slightly_smiling_face:

We can start with this repository to move forward quickly, but it would be nice to be able to move it in the future under an organization managed by the community, it can be the open-web3-stack organization or another one :slight_smile:

Yes, it’s perfectly fine to quote/recommend the pre-image pallet in any documentation/examples/tutorials around the NFTs transfer, I’m just saying that it shouldn’t be integrated in the formal specifications.

This is not how the reserve based model works. Your description corresponds more to a teleportation between two networks that totally trust each other.
In the reserve-based model, the reserve chain is the only source of truth, and the only one on which the “real” asset is located. The other chains only mint/burn derivatives representations.

This is why in all cases the reserve chain must keep the NFT metadata and check that any modifications to it comply with the business rules of the NFT concerned (defined by the smart contract that handle this NFT or by the runtime itself through a pallet).
The fact that a remote chain becomes the owner of the asset (via its sovereign account), does not mean that it can modify the design or operation rules of this asset.
All these considerations are not specific to moonbeam, they are inherent to the “reserve based transfer” model.

It does not impose any, the derived chain can modify what it wants on its own network, but if it wants its changes to be taken into account by the reserve chain (and therefore by any other chain that derive this NFT), it needs the agreement of the reserve chain.

Yes, the NFT can have the old one + some extension, I agree. But a chain that has its own extension for NFTs metadata cannot force other chains to recognize this extension.
When the destination chain receives a message from the reserve chain, it trusts the reserve chain and considers as true everything the reserve chain sends. So the reserve chain can only send data that it has been able to verify itself.
If the destination chain wants to recognize an extension of the sending chain but this extension is not recognized by the reserve chain, then the 2 chains which are not reserve must define their own protocol to pass the data related to this extension.

Concerning Moonbeam in particular, it is possible to define some contract interfaces allowing to know if a smart contract supports such or such extension provided by such or such parachain/pallet or other, everything is possible, provided that it remains optional.

In the general case, a contract that only complies to ERC721 and nothing else does not allow to modify the metadata, so we must be able to handle this case.

I understand that this is frustrating, but it is necessary to preserve security and ensure that the rules defined by the designer of an NFT are respected.

We will do our best to support the NFT extensions you wish to provide, but we cannot force our users to accept these extensions in their smart contracts, especially since some ERC721 smart contracts are already deployed and not upgradeable.

The specifications for xc-NFTs must assume as little as possible if they are to be widely adopted, so they cannot assume support for this or that way of modifying metadata.

I think it is better for the user if the transfer fails as soon as possible in case of incompatibility, to avoid wasting time and money for nothing.

What the reserve chain can optionally do (but it doesn’t have to be mandatory) is to store a request for metadata modification while waiting for someone (basically any signed origin) to inject the whole new metadata, the reserve chain then checks if the state transition respects its rules, there are then 3 possible results:

  1. The metadata change is accepted: the reserve chain then performs 3 distinct operations (the reserve chain must first check that enough fees have been paid for all these operations):
  • forward the XCM message in the request to the destination chain (with a topic that contains the new metadata hash)
  • send back an XCM message to the initial sender chain (to unlock and burn the derivative on the sender chain)
  • Move the NFT ownership from the sovereign account of the sender chain to the sovereign account of the destination chain
  1. The metadata change is not accepted: the reserve chain then perform one operation (the reserve chain must first check that enough fees have been paid for this operation):
  • Send back an XCM message to the initial sender chain with the error, the initial sender chain can either unlock the NFT and cancel the transfer or still the NFT locked and rely on its governance or any other process to find a solution.
  1. Not enough costs have been provisioned by the user who initially requested the transfer or/and the user who injected the new metadata on the reserve chain: the metadata injection action/extrinsic must fail with an error like “TooExpensive”.

With such a system, what we could do on the Moonbeam side when we receive an “xc-NFT XCM message”, is to ask to the contract that handle this NFT if it support an interface (to be defined) to mutate the metadata in the “xc-NFT way” (we can do that thanks to EIP165).
If this is the case, we store the request and follow the process I described above, but if it’s not the case, we must throw an error and interrupt the NFT transfer.

Obviously, the reserve chain should first verify that the new hash is not the same as the old one, if it’s the same, the reserve chain should forward the transfer directly.

This imply 3 user actions for transfer from a non-reserve chain to a non-reserve chain, but I think we can’t do better.