Just an first draft about what the XCM messages could look like (pseudo-code):
From reserve to non-reserve
Message send by the reserve chain to the non-reserve chain:
Xcm([
ReserveAssetDeposited(feeAssetAndNft)
ClearOrigin
BuyExecution { fees: feeAsset, weight_limit }
SetTopic(metadataHash)
DepositAsset(assets: all, beneficiary)
])
From non-reserve to reserve (without metadata mutation)
Message send by the non-reserve chain to the reserve chain:
Xcm([
WithdrawAsset(feeAssetAndNft)
ClearOrigin
BuyExecution { fees: feeAsset, weight_limit }
SetAppendix([ReportError({ destination: sender, query_id, max_weight})])
DepositAsset(assets: all, beneficiary)
])
From non-reserve to reserve (with metadata mutation)
Message send by the non-reserve chain to the reserve chain:
Xcm([
WithdrawAsset(feeAssetAndNft)
DescendOrigin(userAccountOnSenderChain)
BuyExecution { fees: feeAsset, weight_limit }
SetErrorHandler([ReportError({ destination: sender, query_id, max_weight})])
DepositAsset(assets: all, beneficiary: origin)
Transact(xnft.mutateThenDeposit(nftAsset, newMetadataHash, destAccount, response: { sender, query_id }))
])
The call xnft.mutateThenDeposit
can either:
- Accept the new metadata hash direcly and deposit the NFT on the destAccount, in that case the call should send to the sender chain the XCM message
Xcm([QueryResponse { queryi_id, response: DispatchResult(Success), .. }])
. - Create a pending request and lock the NFT, waiting for whole metatada injection.
- Reject the new metadata hash direcly and throw an XCM error
In case of 2, a response will be send back to the sender chain only at metadata injection on the reserve or call to xnft.mutateRequestExpire
(by any signed origin) after the expiration delay.
This allow to send back a response to the sender chain only when the mutate request is finally accepted or rejected.
From non-reserve to non-reserve
Message send by the non-reserve sender chain to the reserve chain:
Xcm([
WithdrawAsset(feeAssetAndNft)
DescendOrigin(userAccountOnSenderChain)
BuyExecution { fees: feeAsset, weight_limit }
SetErrorHandler([ReportError({ destination: sender, query_id, max_weight})])
DepositAsset(assets: all, beneficiary: origin)
Transact(xnft.mutateThenSend(
nftAsset,
newMetadataHash,
dest,
fees: Some((feeAsset, weight_limit)),
response: { sender, query_id }
xcm: Xcm([
DepositAsset(assets: all, beneficiary)]))
]),
)),
])
The call xnft.mutateThenSend
can either:
- Accept the new metadata hash direcly and forward the inner xcm message to the destination, in that case the call should:
- prepend the inner message with 3 instructions (
[WidrawAsset(feeAssetAndNft),ClearOrigin,SetTopic(newMetadataHash)]
) and send it to the destination chain. - send to the sender chain the XCM message
Xcm([QueryResponse { queryi_id, response: DispatchResult(Success), .. }])
. - move the NFT ownership to the sovereign account of the dest chain.
- Create a pending request and lock the NFT, waiting for whole metatada injection.
- Reject the new metadata hash direcly and throw an XCM error
In case of 2, a response will be send back to the sender chain only at metadata injection on the reserve or call to xnft.mutateRequestExpire
(by any signed origin) after the expiration delay.
This allow to send back a response to the sender chain only when the mutate request is finally accepted or rejected.
All the call names can obviously be changed, the relevant part is not about the names but the structure of the process.
It seem’s that we need at least 3 calls in the common pallet (and probably a 4th to just mutate
metadata without any cross-chain transfer), that can be the existing “xtokens” pallet but I’m afraid that the xtokens pallet will become too heavy.
It seem’s better to me to create a new dedicated pallet because transferring NTFs need a lot of specific stuff to handle metadata that will pollute the xtokens pallet for project that don’t need to handle NFTs transfers through XCM.