# Basic NFT Vault with Transfer Spec ###### tags: `NFTs` `Aztec Connect` `Solidity` ## Spec 1. What does this bridge do? Why did you build it? This bridge allows users to deposit and withdraw NFTs to/from Aztec Connect as well as send their NFTs to other bridge contracts. A user may want to send their NFT to another bridge contract because it may have additional functionality (swapping, minting, etc). 2. What protocol(s) does the bridge interact with? [ERC-721 contracts](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol) on Etheruem. ERC-721 contracts are the most widely used NFT standard. A bridge that supports 721 contracts should be able to handle most NFTs. 3. What is the flow of the bridge? - What actions does this bridge make possible? - Depositing NFTs to Aztec from an Ethereum account - Withdrawing NFTs from Aztec to an Ethereum account - Transfering NFTs around the Aztec network, anonymously - Are all bridge interactions synchronous, or is the a asynchronous flow? - all interactions are synchronous - For each interaction define: - All input + output tokens, including [AztecType](https://github.com/AztecProtocol/aztec-connect-bridges/blob/master/src/aztec/libraries/AztecTypes.sol) info - Deposit: - input: ETH - output: VIRTUAL - Withdrawal: - input: VIRTUAL - output: ETH (not used, == 0) - Transfer - input: VIRTUAL - output: VIRTUAL - All relevant bridge address ids (may fill in after deployment) - use of auxData - Deposit: not used - Withdrawal: id of the Ethereum address to withdraw to. Ids are stored in the AddressRegistry contract, https://hackmd.io/uCwDcXJOTLSsIIW7L5_CCw?view - Transfer: the NFT bridge contract to send the NFT to - gas usage - TBD - cases that would make the interaction revert (low liquidity etc) - Eth address to withdraw to is not registered - Please include any diagrams that would be helpful to understand asset flow. 4. Please list any edge cases that may restrict the usefulness of the bridge or that the bridge prevents explicit. As written, this bridge has very little functionality. We will think about how to extend this functionality in future NFT specs. Providing privacy for NFTs is also tricky because each NFT is unique. Aztec users typically get privacy by hiding a crowd of users, but with NFTs the size of the crowd is 1. 5. How can the accounting of the bridge be impacted by interactions performed by other parties than the bridge? Example, if borrowing, how does it handle liquidations etc. 6. What functions are available in /src/client? How should they be used? - `getAuxData` - should return the range of registered eth addresses for withdrawal - or - should return the range of whitelisted bridge contracts - `getExpectedOutput` - this should always return 1 output for deposits - should always return 0 outputs for withdrawals - should always return 1 output for transfers - `getInteractionPresentValue` - this should always return 1 for a VIRTUAL asset that is mapped to an NFT 7. Is this contract upgradable? If so, what are the restrictions on upgradability? No 8. Does this bridge maintain state? If so, what is stored and why? Yes. The bridge stores a few things in state * Nft Assets * These are structs that map an nft id (unique to the contract) to the corresponding NFT info (collection, tokenId, virtual asset id) * whitelist 9. Any other relevant information ## NftVault.sol ### Structs Nft data is stored in a NftAsset struct ```solidity struct NftAsset { address collection; uint256 tokenId; } ``` ### Mappings ```solidity! // virtual asset id => NftAsset mapping(uint256 => NftAsset) public nftAssets; ``` ### Functions #### convert Cases: - deposit - Assets: (ETH in (1 wei), VIRTUAL out) - auxData: none - withdraw - Assets: (VIRTUAL in, ETH (not used) out) - auxData: withdraw address id, 64 bits? - transfer - assets: - VIRTUAL in - VIRUTAL out - auxData - id of the bridge contract to send the nft to ```solidity! // DEPOSIT // return virutal asset id, will not actually match to NFT until matchDeposit is called from ethereum if( _inputAssetA.assetType == AztecTypes.AztecAssetType.ETH && _outputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL ) { require(_totalInputValue == 1, "send only 1 wei"); tokens[_interactionNonce] = NftAsset({ collection: address(0x0), id: 0 }); return (1, 0, false); } // WITHDRAW else if ( _inputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL && _outputAssetA.assetType == AztecTypes.AztecAssetType.ETH ) { NftAsset token = tokens[inputAssetA.id]; require(token.collection != address(0x0), "NFT doesn't exist"); address _to = registry.addresses(_auxData); require(_to != address(0x0), "unregistered withdraw address"); NFT(token.collection).transferFrom(this, _to, token.id); delete tokens[inputAssetA.id]; return (0, 0, false); } // TRANSFER TO OTHER BRIDGE CONTRACT else if ( _inputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL && _outputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL ) { NftAsset token = tokens[inputAssetA.id]; require(token.collection != address(0x0), "NFT doesn't exist"); address to = AddressRegistry.addresses(auxData); delete tokens[inputAssetA.id]; Nft(token.collection).approve(to, token.id); NftVault(to).matchDeposit(_interactionNonce, token.collection, token.id); return (1, 0, false); } ``` #### Match Deposit User Flow: 1. Aztec account triggers a deposit 2. Eth account approves this bridge to take their NFT 3. Eth account calls `matchDeposit`, which takes the NFT and holds it, matching the owner to the virtual asset id ```solidity! function matchDeposit(uint256 _virtualAssetId, address _collection, uint256 _tokenId) external { if (nftAssets[_virtualAssetId].collection != address(0x0)) { revert InvalidVirtualAssetId(); } nftAssets[_virtualAssetId] = NftAsset({collection: _collection, tokenId: _tokenId}); IERC721(_collection).transferFrom(msg.sender, address(this), _tokenId); emit NftDeposit(_virtualAssetId, _collection, _tokenId); } ```