160 lines
6.5 KiB
Markdown
160 lines
6.5 KiB
Markdown
# NFT bridge App
|
|
|
|
[TOC]
|
|
|
|
## Objective
|
|
|
|
To use the Wormhole message passing protocol to transfer NFTs between different connected chains.
|
|
|
|
## Background
|
|
|
|
NFTs are a new asset class that has grown in popularity recently. It especially attracts new users and companies to
|
|
crypto. NFTs, just like traditional tokens, are minted on a single blockchain and cannot be transferred to other chains.
|
|
Howevre as more chains introduce NFT standards and marketplaces there is demand for ways to transfer NFTS across chains
|
|
to access these markets and collect them in a single wallet.
|
|
|
|
## Goals
|
|
|
|
We want to implement a generalized NFT bridge using the Wormhole message passing protocol that is able to bridge any
|
|
standards-compliant NFT between chains, creating unique wrapped representations on each connected chain on demand.
|
|
|
|
* Allow transfer of standards-compliant NFTs between chains.
|
|
* Use a universal NFT representation that is compatible with most VM data types.
|
|
|
|
## Non-Goals
|
|
|
|
* Support EIP1155
|
|
* Manage / Transfer chain-specific NFT metadata that isn't broadly applicable to all chains.
|
|
* Automatically relay NFT transfer messages to the target chain.
|
|
|
|
## Overview
|
|
|
|
On each chain of the NFT bridge network there will be a NFT bridge endpoint program.
|
|
|
|
These programs will manage authorization of payloads (emitter filtering), wrapped representations of foreign chain
|
|
NFTs ("Wrapped NFTs") and custody locked NFTs.
|
|
|
|
We aim to support:
|
|
|
|
- EIP721 with token_uri extension: Ethereum, BSC
|
|
- Metaplex SPL Meta: Solana
|
|
- CW721 with token_uri extension: Terra
|
|
|
|
## Detailed Design
|
|
|
|
For outbound transfers, the contracts will have a lock method that either locks up a native NFT and produces a
|
|
respective Transfer message that is posted to Wormhole, or burns a wrapped NFT and produces/posts said message.
|
|
|
|
For inbound transfers they can consume, verify and process Wormhole messages containing a NFT bridge payload.
|
|
|
|
There will be three different payloads:
|
|
|
|
* Transfer - Will trigger the release of locked NFTs or minting of wrapped NFTs.
|
|
|
|
Identical to the NFT bridge:
|
|
|
|
* RegisterChain - Register the NFT bridge contract (emitter address) for a foreign chain.
|
|
* UpgradeContract - Upgrade the contract.
|
|
|
|
In order to transfer an NFT to another chain, a user needs to call the transfer method of the bridge contract with the
|
|
recipient details. The contract will either hold the NFTs in a custody account (in case it is a native NFT) or burn
|
|
wrapped NFTs. Wrapped NFTs can be burned because they can be freely minted once they are transferred back. After the
|
|
lockup the contract will post a Transfer payload message to Wormhole. Once the message has been signed by the guardians,
|
|
it can be posted to the target chain of the transfer. The target chain will then either release the native NFT from
|
|
custody or mint a wrapped NFT depending on whether it's a native NFT there. The program will keep track of consumed
|
|
message digests for replay prevention.
|
|
|
|
Since the method for posting a VAA to the NFT bridge is authorized by the message signature itself, anyone can post any
|
|
message.
|
|
|
|
Since every NFT has unique metadata the Transfer messages contain all metadata, a transfer (even the first on per NFT)
|
|
only requires a single Wormhole message to be passed compared to the Token Bridge. On the first transfer action of an
|
|
NFT (address / symbol / name) a wrapped asset (i.e. master edition or new contract) is created. When the wrapped asset (
|
|
contract) is already initialized or was just initialized, the (new) token_id and metadata URI are registered.
|
|
|
|
### API / database schema
|
|
|
|
Proposed bridge interface:
|
|
|
|
transfer(address token, uint256 token_id, uint16 recipient_chain, bytes32 recipient) - Initiate a Transfer
|
|
|
|
completeTransfer(Message transfer) - Execute a Transfer message
|
|
|
|
registerChain(Message registerChain) - Execute a RegisterChain governance message
|
|
|
|
upgrade(Message upgrade) - Execute a UpgradeContract governance message
|
|
|
|
---
|
|
Payloads:
|
|
|
|
Transfer:
|
|
|
|
```
|
|
PayloadID uint8 = 1
|
|
// Address of the NFT. Left-zero-padded if shorter than 32 bytes
|
|
NFTAddress [32]uint8
|
|
// Chain ID of the NFT
|
|
NFTChain uint16
|
|
// Symbol of the NFT
|
|
Symbol [32]uint8
|
|
// Name of the NFT
|
|
Name [32]uint8
|
|
// ID of the token (big-endian uint256)
|
|
TokenID [32]uint8
|
|
// URI of the NFT. Valid utf8 string, maximum 200 bytes.
|
|
URILength u8
|
|
URI [n]uint8
|
|
// Address of the recipient. Left-zero-padded if shorter than 32 bytes
|
|
To [32]uint8
|
|
// Chain ID of the recipient
|
|
ToChain uint16
|
|
```
|
|
|
|
RegisterChain:
|
|
|
|
```
|
|
PayloadID uint8 = 2
|
|
// Chain ID
|
|
ChainID uint16
|
|
// Emitter address. Left-zero-padded if shorter than 32 bytes
|
|
EmitterAddress [32]uint8
|
|
```
|
|
|
|
UpgradeContract:
|
|
|
|
```
|
|
PayloadID uint8 = 3
|
|
// Address of the new contract
|
|
NewContract [32]uint8
|
|
```
|
|
|
|
## Caveats
|
|
|
|
There is no guarantee for completion of transfers. If a user initiates a transfer and doesn't call completeTransfer on
|
|
the target chain, a transfer might not be completed. In case a guardian set change happens in-between and the original
|
|
signer guardian set expires, the transfer will be stuck indefinitely.
|
|
|
|
Since there is no way for a NFT bridge endpoint to know which other chain already has wrapped assets set up for the
|
|
native asset on its chain, there may be transfers initiated for assets that don't have wrapped assets set up yet on the
|
|
target chain. However, the transfer will become executable once the wrapped asset is set up (which can be done any time)
|
|
.
|
|
|
|
The name and symbol fields of the Transfer payload are not guaranteed to be
|
|
valid UTF8 strings. Implementations might truncate longer strings at the 32 byte
|
|
mark, which may result in invalid UTF8 bytes at the end. Thus, any client
|
|
whishing to present these as strings must validate them first, potentially
|
|
dropping the garbage at the end.
|
|
|
|
Currently Solana only supports u64 token ids which is incompatible with Ethereum which specifically mentions the use of
|
|
UUIDs as token ids (utilizing all bytes of the uint256). There will either need to be a mechanism to translate ids i.e.
|
|
a map of `[32]u8 -> incrementing_u64` (in the expectation there will never be more than MaxU64 editions) or Solana needs
|
|
to change their NFT contract.
|
|
|
|
Terra CW721 contracts support arbitrary strings as token IDs. In order to fit
|
|
them into 32 bytes, we store their keccak256 hash instead. This means that when
|
|
transferring a terra-native NFT through the wormhole, the ID of the output token
|
|
will be the original token's hash. However, wrapped assets on terra will retain
|
|
their original token ids, simply stringified into a decimal number. Then,
|
|
when transferring them back through the wormhole, we can guarantee that these
|
|
ids will parse as a uint256.
|