diff --git a/.changeset/happy-carpets-rush.md b/.changeset/happy-carpets-rush.md
new file mode 100644
index 0000000000000000000000000000000000000000..249dbedbb6d30615a3b9c030a0826552e210e829
--- /dev/null
+++ b/.changeset/happy-carpets-rush.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/contracts-periphery': patch
+---
+
+Simplify, cleanup, and standardize ERC721 bridge contracts.
diff --git a/integration-tests/contracts/FakeL2StandardERC721.sol b/integration-tests/contracts/FakeOptimismMintableERC721.sol
similarity index 56%
rename from integration-tests/contracts/FakeL2StandardERC721.sol
rename to integration-tests/contracts/FakeOptimismMintableERC721.sol
index 30d1a2e898fa53c3b6917d924fd614795b1a346e..2108dbbfc373b86ca3c86ee39f6e962648a20a80 100644
--- a/integration-tests/contracts/FakeL2StandardERC721.sol
+++ b/integration-tests/contracts/FakeOptimismMintableERC721.sol
@@ -3,14 +3,14 @@ pragma solidity ^0.8.9;
 
 import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
 
-contract FakeL2StandardERC721 is ERC721 {
+contract FakeOptimismMintableERC721 is ERC721 {
 
-    address public immutable l1Token;
-    address public immutable l2Bridge;
+    address public immutable remoteToken;
+    address public immutable bridge;
 
-    constructor(address _l1Token, address _l2Bridge) ERC721("FakeERC721", "FAKE") {
-        l1Token = _l1Token;
-        l2Bridge = _l2Bridge;
+    constructor(address _remoteToken, address _bridge) ERC721("FakeERC721", "FAKE") {
+        remoteToken = _remoteToken;
+        bridge = _bridge;
     }
 
     function mint(address to, uint256 tokenId) public {
diff --git a/integration-tests/test/nft-bridge.spec.ts b/integration-tests/test/nft-bridge.spec.ts
index 3ff1abaf5bf3f364fbe7d8b9aae30b185e8f55cb..74abda59f857b4e010086dba5ec060f4209a9eb8 100644
--- a/integration-tests/test/nft-bridge.spec.ts
+++ b/integration-tests/test/nft-bridge.spec.ts
@@ -5,8 +5,8 @@ import { predeploys } from '@eth-optimism/contracts'
 import Artifact__TestERC721 from '@eth-optimism/contracts-periphery/artifacts/contracts/testing/helpers/TestERC721.sol/TestERC721.json'
 import Artifact__L1ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L1/messaging/L1ERC721Bridge.sol/L1ERC721Bridge.json'
 import Artifact__L2ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L2/messaging/L2ERC721Bridge.sol/L2ERC721Bridge.json'
-import Artifact__L2StandardERC721Factory from '@eth-optimism/contracts-periphery/artifacts/contracts/L2/messaging/L2StandardERC721Factory.sol/L2StandardERC721Factory.json'
-import Artifact__L2StandardERC721 from '@eth-optimism/contracts-periphery/artifacts/contracts/standards/L2StandardERC721.sol/L2StandardERC721.json'
+import Artifact__OptimismMintableERC721Factory from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol/OptimismMintableERC721Factory.json'
+import Artifact__OptimismMintableERC721 from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/op-erc721/OptimismMintableERC721.sol/OptimismMintableERC721.json'
 
 /* Imports: Internal */
 import { expect } from './shared/setup'
@@ -49,7 +49,7 @@ describe('ERC721 Bridge', () => {
   let Factory__L1ERC721: ContractFactory
   let Factory__L1ERC721Bridge: ContractFactory
   let Factory__L2ERC721Bridge: ContractFactory
-  let Factory__L2StandardERC721Factory: ContractFactory
+  let Factory__OptimismMintableERC721Factory: ContractFactory
   before(async () => {
     Factory__L1ERC721 = await ethers.getContractFactory(
       Artifact__TestERC721.abi,
@@ -66,9 +66,9 @@ describe('ERC721 Bridge', () => {
       Artifact__L2ERC721Bridge.bytecode,
       bobWalletL2
     )
-    Factory__L2StandardERC721Factory = await ethers.getContractFactory(
-      Artifact__L2StandardERC721Factory.abi,
-      Artifact__L2StandardERC721Factory.bytecode,
+    Factory__OptimismMintableERC721Factory = await ethers.getContractFactory(
+      Artifact__OptimismMintableERC721Factory.abi,
+      Artifact__OptimismMintableERC721Factory.bytecode,
       bobWalletL2
     )
   })
@@ -76,48 +76,58 @@ describe('ERC721 Bridge', () => {
   let L1ERC721: Contract
   let L1ERC721Bridge: Contract
   let L2ERC721Bridge: Contract
-  let L2StandardERC721Factory: Contract
-  let L2StandardERC721: Contract
+  let OptimismMintableERC721Factory: Contract
+  let OptimismMintableERC721: Contract
   beforeEach(async () => {
     L1ERC721 = await Factory__L1ERC721.deploy()
     await L1ERC721.deployed()
 
-    L2ERC721Bridge = await Factory__L2ERC721Bridge.deploy(
-      predeploys.L2CrossDomainMessenger
-    )
-    await L2ERC721Bridge.deployed()
-
     L1ERC721Bridge = await Factory__L1ERC721Bridge.deploy(
       env.messenger.contracts.l1.L1CrossDomainMessenger.address,
-      L2ERC721Bridge.address
+      ethers.utils.getContractAddress({
+        from: await Factory__L2ERC721Bridge.signer.getAddress(),
+        nonce: await Factory__L2ERC721Bridge.signer.getTransactionCount(),
+      })
     )
     await L1ERC721Bridge.deployed()
 
-    L2StandardERC721Factory = await Factory__L2StandardERC721Factory.deploy(
+    L2ERC721Bridge = await Factory__L2ERC721Bridge.deploy(
+      predeploys.L2CrossDomainMessenger,
+      L1ERC721Bridge.address
+    )
+    await L2ERC721Bridge.deployed()
+
+    OptimismMintableERC721Factory =
+      await Factory__OptimismMintableERC721Factory.deploy(
+        L2ERC721Bridge.address
+      )
+    await OptimismMintableERC721Factory.deployed()
+
+    expect(await L1ERC721Bridge.otherBridge()).to.equal(L2ERC721Bridge.address)
+    expect(await L2ERC721Bridge.otherBridge()).to.equal(L1ERC721Bridge.address)
+
+    expect(await OptimismMintableERC721Factory.bridge()).to.equal(
       L2ERC721Bridge.address
     )
-    await L2StandardERC721Factory.deployed()
 
     // Create a L2 Standard ERC721 with the Standard ERC721 Factory
-    const tx = await L2StandardERC721Factory.createStandardL2ERC721(
-      L1ERC721.address,
-      'L2ERC721',
-      'L2'
-    )
-    await tx.wait()
+    const tx =
+      await OptimismMintableERC721Factory.createStandardOptimismMintableERC721(
+        L1ERC721.address,
+        'L2ERC721',
+        'L2'
+      )
+    const receipt = await tx.wait()
 
-    // Retrieve the deployed L2 Standard ERC721
-    const L2StandardERC721Address =
-      await L2StandardERC721Factory.standardERC721Mapping(L1ERC721.address)
-    L2StandardERC721 = await ethers.getContractAt(
-      Artifact__L2StandardERC721.abi,
-      L2StandardERC721Address
-    )
-    await L2StandardERC721.deployed()
+    // Get the OptimismMintableERC721Created event
+    const erc721CreatedEvent = receipt.events[0]
+    expect(erc721CreatedEvent.event).to.be.eq('OptimismMintableERC721Created')
 
-    // Initialize the L2 bridge contract
-    const tx1 = await L2ERC721Bridge.initialize(L1ERC721Bridge.address)
-    await tx1.wait()
+    OptimismMintableERC721 = await ethers.getContractAt(
+      Artifact__OptimismMintableERC721.abi,
+      erc721CreatedEvent.args.localToken
+    )
+    await OptimismMintableERC721.deployed()
 
     // Mint an L1 ERC721 to Bob on L1
     const tx2 = await L1ERC721.mint(bobAddress, TOKEN_ID)
@@ -128,11 +138,11 @@ describe('ERC721 Bridge', () => {
     await tx3.wait()
   })
 
-  it('depositERC721', async () => {
+  it('bridgeERC721', async () => {
     await env.messenger.waitForMessageReceipt(
-      await L1ERC721Bridge.depositERC721(
+      await L1ERC721Bridge.bridgeERC721(
         L1ERC721.address,
-        L2StandardERC721.address,
+        OptimismMintableERC721.address,
         TOKEN_ID,
         FINALIZATION_GAS,
         NON_NULL_BYTES
@@ -143,14 +153,14 @@ describe('ERC721 Bridge', () => {
     expect(await L1ERC721.ownerOf(TOKEN_ID)).to.equal(L1ERC721Bridge.address)
 
     // Bob owns the NFT on L2
-    expect(await L2StandardERC721.ownerOf(TOKEN_ID)).to.equal(bobAddress)
+    expect(await OptimismMintableERC721.ownerOf(TOKEN_ID)).to.equal(bobAddress)
   })
 
-  it('depositERC721To', async () => {
+  it('bridgeERC721To', async () => {
     await env.messenger.waitForMessageReceipt(
-      await L1ERC721Bridge.depositERC721To(
+      await L1ERC721Bridge.bridgeERC721To(
         L1ERC721.address,
-        L2StandardERC721.address,
+        OptimismMintableERC721.address,
         aliceAddress,
         TOKEN_ID,
         FINALIZATION_GAS,
@@ -162,15 +172,17 @@ describe('ERC721 Bridge', () => {
     expect(await L1ERC721.ownerOf(TOKEN_ID)).to.equal(L1ERC721Bridge.address)
 
     // Alice owns the NFT on L2
-    expect(await L2StandardERC721.ownerOf(TOKEN_ID)).to.equal(aliceAddress)
+    expect(await OptimismMintableERC721.ownerOf(TOKEN_ID)).to.equal(
+      aliceAddress
+    )
   })
 
-  withdrawalTest('withdrawERC721', async () => {
+  withdrawalTest('bridgeERC721', async () => {
     // Deposit an NFT into L2 so that there's something to withdraw
     await env.messenger.waitForMessageReceipt(
-      await L1ERC721Bridge.depositERC721(
+      await L1ERC721Bridge.bridgeERC721(
         L1ERC721.address,
-        L2StandardERC721.address,
+        OptimismMintableERC721.address,
         TOKEN_ID,
         FINALIZATION_GAS,
         NON_NULL_BYTES
@@ -181,10 +193,11 @@ describe('ERC721 Bridge', () => {
     expect(await L1ERC721.ownerOf(TOKEN_ID)).to.equal(L1ERC721Bridge.address)
 
     // Also check that Bob owns the NFT on L2 initially
-    expect(await L2StandardERC721.ownerOf(TOKEN_ID)).to.equal(bobAddress)
+    expect(await OptimismMintableERC721.ownerOf(TOKEN_ID)).to.equal(bobAddress)
 
-    const tx = await L2ERC721Bridge.withdrawERC721(
-      L2StandardERC721.address,
+    const tx = await L2ERC721Bridge.bridgeERC721(
+      OptimismMintableERC721.address,
+      L1ERC721.address,
       TOKEN_ID,
       0,
       NON_NULL_BYTES
@@ -196,15 +209,15 @@ describe('ERC721 Bridge', () => {
     expect(await L1ERC721.ownerOf(TOKEN_ID)).to.equal(bobAddress)
 
     // L2 NFT is burned
-    await expect(L2StandardERC721.ownerOf(TOKEN_ID)).to.be.reverted
+    await expect(OptimismMintableERC721.ownerOf(TOKEN_ID)).to.be.reverted
   })
 
-  withdrawalTest('withdrawERC721To', async () => {
+  withdrawalTest('bridgeERC721To', async () => {
     // Deposit an NFT into L2 so that there's something to withdraw
     await env.messenger.waitForMessageReceipt(
-      await L1ERC721Bridge.depositERC721(
+      await L1ERC721Bridge.bridgeERC721(
         L1ERC721.address,
-        L2StandardERC721.address,
+        OptimismMintableERC721.address,
         TOKEN_ID,
         FINALIZATION_GAS,
         NON_NULL_BYTES
@@ -215,10 +228,11 @@ describe('ERC721 Bridge', () => {
     expect(await L1ERC721.ownerOf(TOKEN_ID)).to.equal(L1ERC721Bridge.address)
 
     // Also check that Bob owns the NFT on L2 initially
-    expect(await L2StandardERC721.ownerOf(TOKEN_ID)).to.equal(bobAddress)
+    expect(await OptimismMintableERC721.ownerOf(TOKEN_ID)).to.equal(bobAddress)
 
-    const tx = await L2ERC721Bridge.withdrawERC721To(
-      L2StandardERC721.address,
+    const tx = await L2ERC721Bridge.bridgeERC721To(
+      OptimismMintableERC721.address,
+      L1ERC721.address,
       aliceAddress,
       TOKEN_ID,
       0,
@@ -231,7 +245,7 @@ describe('ERC721 Bridge', () => {
     expect(await L1ERC721.ownerOf(TOKEN_ID)).to.equal(aliceAddress)
 
     // L2 NFT is burned
-    await expect(L2StandardERC721.ownerOf(TOKEN_ID)).to.be.reverted
+    await expect(OptimismMintableERC721.ownerOf(TOKEN_ID)).to.be.reverted
   })
 
   withdrawalTest(
@@ -239,9 +253,9 @@ describe('ERC721 Bridge', () => {
     async () => {
       // First, deposit the legitimate L1 NFT.
       await env.messenger.waitForMessageReceipt(
-        await L1ERC721Bridge.depositERC721(
+        await L1ERC721Bridge.bridgeERC721(
           L1ERC721.address,
-          L2StandardERC721.address,
+          OptimismMintableERC721.address,
           TOKEN_ID,
           FINALIZATION_GAS,
           NON_NULL_BYTES
@@ -253,25 +267,26 @@ describe('ERC721 Bridge', () => {
       // Deploy a fake L2 ERC721, which:
       // - Returns the address of the legitimate L1 token from its l1Token() getter.
       // - Allows the L2 bridge to call its burn() function.
-      const FakeL2StandardERC721 = await (
-        await ethers.getContractFactory('FakeL2StandardERC721', bobWalletL2)
+      const FakeOptimismMintableERC721 = await (
+        await ethers.getContractFactory('FakeOptimismMintableERC721', bobWalletL2)
       ).deploy(L1ERC721.address, L2ERC721Bridge.address)
-      await FakeL2StandardERC721.deployed()
+      await FakeOptimismMintableERC721.deployed()
 
       // Use the fake contract to mint Alice an NFT with the same token ID
-      const tx = await FakeL2StandardERC721.mint(aliceAddress, TOKEN_ID)
+      const tx = await FakeOptimismMintableERC721.mint(aliceAddress, TOKEN_ID)
       await tx.wait()
 
       // Check that Alice owns the NFT from the fake ERC721 contract
-      expect(await FakeL2StandardERC721.ownerOf(TOKEN_ID)).to.equal(
+      expect(await FakeOptimismMintableERC721.ownerOf(TOKEN_ID)).to.equal(
         aliceAddress
       )
 
       // Alice withdraws the NFT from the fake contract to L1, hoping to receive the legitimate L1 NFT.
       const withdrawalTx = await L2ERC721Bridge.connect(
         aliceWalletL2
-      ).withdrawERC721(
-        FakeL2StandardERC721.address,
+      ).bridgeERC721(
+        FakeOptimismMintableERC721.address,
+        L1ERC721.address,
         TOKEN_ID,
         0,
         NON_NULL_BYTES
diff --git a/packages/contracts-periphery/.depcheckrc b/packages/contracts-periphery/.depcheckrc
index 28241fd9eee325421f01a16b92d46b64305a76f3..b882779bd5a92004f9656e132a778eab4c6995b9 100644
--- a/packages/contracts-periphery/.depcheckrc
+++ b/packages/contracts-periphery/.depcheckrc
@@ -1,5 +1,6 @@
 ignores: [
   "@openzeppelin/contracts",
+  "@openzeppelin/contracts-upgradeable",
   "@rari-capital/solmate",
   "@types/node",
   "hardhat-deploy",
diff --git a/packages/contracts-periphery/contracts/L1/messaging/IL1ERC721Bridge.sol b/packages/contracts-periphery/contracts/L1/messaging/IL1ERC721Bridge.sol
deleted file mode 100644
index 5d06cc9e67db1e121488cc75e951f625768ff4c0..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/contracts/L1/messaging/IL1ERC721Bridge.sol
+++ /dev/null
@@ -1,103 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity >0.5.0 <0.9.0;
-
-/**
- * @title IL1ERC721Bridge
- */
-interface IL1ERC721Bridge {
-    /**********
-     * Events *
-     **********/
-
-    event ERC721DepositInitiated(
-        address indexed _l1Token,
-        address indexed _l2Token,
-        address indexed _from,
-        address _to,
-        uint256 _tokenId,
-        bytes _data
-    );
-
-    event ERC721WithdrawalFinalized(
-        address indexed _l1Token,
-        address indexed _l2Token,
-        address indexed _from,
-        address _to,
-        uint256 _tokenId,
-        bytes _data
-    );
-
-    /********************
-     * Public Functions *
-     ********************/
-
-    /**
-     * @dev get the address of the corresponding L2 bridge contract.
-     * @return Address of the corresponding L2 bridge contract.
-     */
-    function l2ERC721Bridge() external returns (address);
-
-    /**
-     * @dev deposit the ERC721 token to the caller on L2.
-     * @param _l1Token Address of the L1 ERC721 we are depositing
-     * @param _l2Token Address of the L1 respective L2 ERC721
-     * @param _tokenId Token ID of the ERC721 to deposit
-     * @param _l2Gas Gas limit required to complete the deposit on L2.
-     * @param _data Optional data to forward to L2. This data is provided
-     *        solely as a convenience for external contracts. Aside from enforcing a maximum
-     *        length, these contracts provide no guarantees about its content.
-     */
-    function depositERC721(
-        address _l1Token,
-        address _l2Token,
-        uint256 _tokenId,
-        uint32 _l2Gas,
-        bytes calldata _data
-    ) external;
-
-    /**
-     * @dev deposit the ERC721 token to a recipient on L2.
-     * @param _l1Token Address of the L1 ERC721 we are depositing
-     * @param _l2Token Address of the L1 respective L2 ERC721
-     * @param _to L2 address to credit the withdrawal to.
-     * @param _tokenId Token ID of the ERC721 to deposit.
-     * @param _l2Gas Gas limit required to complete the deposit on L2.
-     * @param _data Optional data to forward to L2. This data is provided
-     *        solely as a convenience for external contracts. Aside from enforcing a maximum
-     *        length, these contracts provide no guarantees about its content.
-     */
-    function depositERC721To(
-        address _l1Token,
-        address _l2Token,
-        address _to,
-        uint256 _tokenId,
-        uint32 _l2Gas,
-        bytes calldata _data
-    ) external;
-
-    /*************************
-     * Cross-chain Functions *
-     *************************/
-
-    /**
-     * @dev Complete a withdrawal from L2 to L1, and send the ERC721 token to the recipient on L1
-     * This call will fail if the initialized withdrawal from L2 has not been finalized.
-     *
-     * @param _l1Token Address of L1 token to finalizeWithdrawal for.
-     * @param _l2Token Address of L2 token where withdrawal was initiated.
-     * @param _from L2 address initiating the transfer.
-     * @param _to L1 address to credit the withdrawal to.
-     * @param _tokenId Token ID of the ERC721 to deposit.
-     * @param _data Data provided by the sender on L2. This data is provided
-     *   solely as a convenience for external contracts. Aside from enforcing a maximum
-     *   length, these contracts provide no guarantees about its content.
-     */
-    function finalizeERC721Withdrawal(
-        address _l1Token,
-        address _l2Token,
-        address _from,
-        address _to,
-        uint256 _tokenId,
-        bytes calldata _data
-    ) external;
-}
diff --git a/packages/contracts-periphery/contracts/L1/messaging/L1ERC721Bridge.sol b/packages/contracts-periphery/contracts/L1/messaging/L1ERC721Bridge.sol
index 6365a2d048b50f552064da12e4d0724b40a54410..8f6ce9c2a9afa7fe8f3313b0e6569e37173746c1 100644
--- a/packages/contracts-periphery/contracts/L1/messaging/L1ERC721Bridge.sol
+++ b/packages/contracts-periphery/contracts/L1/messaging/L1ERC721Bridge.sol
@@ -1,138 +1,160 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.9;
 
-/* Interface Imports */
-import { IL1ERC721Bridge } from "./IL1ERC721Bridge.sol";
-import { IL2ERC721Bridge } from "../../L2/messaging/IL2ERC721Bridge.sol";
-import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
-
-/* Library Imports */
 import {
     CrossDomainEnabled
 } from "@eth-optimism/contracts/contracts/libraries/bridge/CrossDomainEnabled.sol";
+import {
+    OwnableUpgradeable
+} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
+import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
 import { Address } from "@openzeppelin/contracts/utils/Address.sol";
+import { L2ERC721Bridge } from "../../L2/messaging/L2ERC721Bridge.sol";
 
 /**
  * @title L1ERC721Bridge
- * @dev The L1 ERC721 Bridge is a contract which stores deposited L1 NFTs that are in use
- * on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits and listening
- * to it for newly finalized withdrawals.
+ * @notice The L1 ERC721 bridge is a contract which works together with the L2 ERC721 bridge to
+ *         make it possible to transfer ERC721 tokens between Optimism and Ethereum. This contract
+ *         acts as an escrow for ERC721 tokens deposted into L2.
  */
-contract L1ERC721Bridge is IL1ERC721Bridge, CrossDomainEnabled {
-    /********************************
-     * External Contract References *
-     ********************************/
-
-    address public l2ERC721Bridge;
+contract L1ERC721Bridge is CrossDomainEnabled, OwnableUpgradeable {
+    /**
+     * @notice Contract version number.
+     */
+    uint8 public constant VERSION = 1;
 
-    // Maps L1 token to L2 token to token ID to a boolean indicating if the token is deposited
-    mapping(address => mapping(address => mapping(uint256 => bool))) public deposits;
+    /**
+     * @notice Emitted when an ERC721 bridge to the other network is initiated.
+     *
+     * @param localToken  Address of the token on this domain.
+     * @param remoteToken Address of the token on the remote domain.
+     * @param from        Address that initiated bridging action.
+     * @param to          Address to receive the token.
+     * @param tokenId     ID of the specific token deposited.
+     * @param extraData   Extra data for use on the client-side.
+     */
+    event ERC721BridgeInitiated(
+        address indexed localToken,
+        address indexed remoteToken,
+        address indexed from,
+        address to,
+        uint256 tokenId,
+        bytes extraData
+    );
 
-    /***************
-     * Constructor *
-     ***************/
+    /**
+     * @notice Emitted when an ERC721 bridge from the other network is finalized.
+     *
+     * @param localToken  Address of the token on this domain.
+     * @param remoteToken Address of the token on the remote domain.
+     * @param from        Address that initiated bridging action.
+     * @param to          Address to receive the token.
+     * @param tokenId     ID of the specific token deposited.
+     * @param extraData   Extra data for use on the client-side.
+     */
+    event ERC721BridgeFinalized(
+        address indexed localToken,
+        address indexed remoteToken,
+        address indexed from,
+        address to,
+        uint256 tokenId,
+        bytes extraData
+    );
 
-    // This contract lives behind a proxy, so the constructor parameters will go unused.
-    constructor(address _l1messenger, address _l2ERC721Bridge) CrossDomainEnabled(address(0)) {
-        _initialize(_l1messenger, _l2ERC721Bridge);
-    }
+    /**
+     * @notice Address of the bridge on the other network.
+     */
+    address public otherBridge;
 
-    /******************
-     * Initialization *
-     ******************/
+    // Maps L1 token to L2 token to token ID to a boolean indicating if the token is deposited
+    /**
+     * @notice Mapping of L1 token to L2 token to ID to boolean, indicating if the given L1 token
+     *         by ID was deposited for a given L2 token.
+     */
+    mapping(address => mapping(address => mapping(uint256 => bool))) public deposits;
 
     /**
-     * @param _l1messenger L1 Messenger address being used for cross-chain communications.
-     * @param _l2ERC721Bridge L2 ERC721 bridge address.
+     * @param _messenger   Address of the CrossDomainMessenger on this network.
+     * @param _otherBridge Address of the ERC721 bridge on the other network.
      */
-    function _initialize(address _l1messenger, address _l2ERC721Bridge) internal {
-        messenger = _l1messenger;
-        l2ERC721Bridge = _l2ERC721Bridge;
+    constructor(address _messenger, address _otherBridge) CrossDomainEnabled(address(0)) {
+        initialize(_messenger, _otherBridge);
     }
 
-    /**************
-     * Depositing *
-     **************/
-
     /**
-     * @inheritdoc IL1ERC721Bridge
+     * @param _messenger   Address of the CrossDomainMessenger on this network.
+     * @param _otherBridge Address of the ERC721 bridge on the other network.
      */
-    function depositERC721(
-        address _l1Token,
-        address _l2Token,
-        uint256 _tokenId,
-        uint32 _l2Gas,
-        bytes calldata _data
-    ) external virtual {
-        // Modifier requiring sender to be EOA.  This check could be bypassed by a malicious
-        // contract via initcode, but it takes care of the user error we want to avoid.
-        require(!Address.isContract(msg.sender), "Account not EOA");
+    function initialize(address _messenger, address _otherBridge) public reinitializer(VERSION) {
+        messenger = _messenger;
+        otherBridge = _otherBridge;
 
-        _initiateERC721Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _tokenId, _l2Gas, _data);
+        // Initialize upgradable OZ contracts
+        __Ownable_init();
     }
 
     /**
-     * @inheritdoc IL1ERC721Bridge
+     * @notice Initiates a bridge of an NFT to the caller's account on L2.
+     *
+     * @param _localToken  Address of the ERC721 on this domain.
+     * @param _remoteToken Address of the ERC721 on the remote domain.
+     * @param _tokenId     Token ID to bridge.
+     * @param _minGasLimit Minimum gas limit for the bridge message on the other domain.
+     * @param _extraData   Optional data to forward to L2. Data supplied here will not be used to
+     *                     execute any code on L2 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
      */
-    function depositERC721To(
-        address _l1Token,
-        address _l2Token,
-        address _to,
+    function bridgeERC721(
+        address _localToken,
+        address _remoteToken,
         uint256 _tokenId,
-        uint32 _l2Gas,
-        bytes calldata _data
-    ) external virtual {
-        _initiateERC721Deposit(_l1Token, _l2Token, msg.sender, _to, _tokenId, _l2Gas, _data);
+        uint32 _minGasLimit,
+        bytes calldata _extraData
+    ) external {
+        // Modifier requiring sender to be EOA. This check could be bypassed by a malicious
+        // contract via initcode, but it takes care of the user error we want to avoid.
+        require(!Address.isContract(msg.sender), "L1ERC721Bridge: account is not externally owned");
+
+        _initiateBridgeERC721(
+            _localToken,
+            _remoteToken,
+            msg.sender,
+            msg.sender,
+            _tokenId,
+            _minGasLimit,
+            _extraData
+        );
     }
 
     /**
-     * @dev Performs the logic for deposits by informing the L2 Deposited Token
-     * contract of the deposit and calling a handler to lock the L1 NFT. (e.g. transferFrom)
+     * @notice Initiates a bridge of an NFT to some recipient's account on L2.
      *
-     * @param _l1Token Address of the L1 ERC721 we are depositing
-     * @param _l2Token Address of the L1 respective L2 ERC721
-     * @param _from Account to pull the deposit from on L1
-     * @param _to Account to give the deposit to on L2
-     * @param _tokenId Token ID of the ERC721 to deposit.
-     * @param _l2Gas Gas limit required to complete the deposit on L2.
-     * @param _data Optional data to forward to L2. This data is provided
-     *        solely as a convenience for external contracts. Aside from enforcing a maximum
-     *        length, these contracts provide no guarantees about its content.
+     * @param _localToken  Address of the ERC721 on this domain.
+     * @param _remoteToken Address of the ERC721 on the remote domain.
+     * @param _to          Address to receive the token on the other domain.
+     * @param _tokenId     Token ID to bridge.
+     * @param _minGasLimit Minimum gas limit for the bridge message on the other domain.
+     * @param _extraData   Optional data to forward to L2. Data supplied here will not be used to
+     *                     execute any code on L2 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
      */
-    function _initiateERC721Deposit(
-        address _l1Token,
-        address _l2Token,
-        address _from,
+    function bridgeERC721To(
+        address _localToken,
+        address _remoteToken,
         address _to,
         uint256 _tokenId,
-        uint32 _l2Gas,
-        bytes calldata _data
-    ) internal {
-        // When a deposit is initiated on L1, the L1 Bridge transfers the NFT to itself for future
-        // withdrawals.
-        // slither-disable-next-line reentrancy-events, reentrancy-benign
-        IERC721(_l1Token).transferFrom(_from, address(this), _tokenId);
-
-        // Construct calldata for _l2Token.finalizeERC721Deposit(_to, _tokenId)
-        bytes memory message = abi.encodeWithSelector(
-            IL2ERC721Bridge.finalizeERC721Deposit.selector,
-            _l1Token,
-            _l2Token,
-            _from,
+        uint32 _minGasLimit,
+        bytes calldata _extraData
+    ) external {
+        _initiateBridgeERC721(
+            _localToken,
+            _remoteToken,
+            msg.sender,
             _to,
             _tokenId,
-            _data
+            _minGasLimit,
+            _extraData
         );
-
-        // Send calldata into L2
-        // slither-disable-next-line reentrancy-events, reentrancy-benign
-        sendCrossDomainMessage(l2ERC721Bridge, _l2Gas, message);
-
-        // slither-disable-next-line reentrancy-benign
-        deposits[_l1Token][_l2Token][_tokenId] = true;
-
-        // slither-disable-next-line reentrancy-events
-        emit ERC721DepositInitiated(_l1Token, _l2Token, _from, _to, _tokenId, _data);
     }
 
     /*************************
@@ -140,29 +162,81 @@ contract L1ERC721Bridge is IL1ERC721Bridge, CrossDomainEnabled {
      *************************/
 
     /**
-     * @inheritdoc IL1ERC721Bridge
+     * @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the
+     *         recipient on this domain.
+     *
+     * @param _localToken  Address of the ERC721 token on this domain.
+     * @param _remoteToken Address of the ERC721 token on the other domain.
+     * @param _from        Address that triggered the bridge on the other domain.
+     * @param _to          Address to receive the token on this domain.
+     * @param _tokenId     ID of the token being deposited.
+     * @param _extraData   Optional data to forward to L2. Data supplied here will not be used to
+     *                     execute any code on L2 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
      */
-    function finalizeERC721Withdrawal(
-        address _l1Token,
-        address _l2Token,
+    function finalizeBridgeERC721(
+        address _localToken,
+        address _remoteToken,
         address _from,
         address _to,
         uint256 _tokenId,
-        bytes calldata _data
-    ) external onlyFromCrossDomainAccount(l2ERC721Bridge) {
+        bytes calldata _extraData
+    ) external onlyFromCrossDomainAccount(otherBridge) {
         // Checks that the L1/L2 token pair has a token ID that is escrowed in the L1 Bridge
         require(
-            deposits[_l1Token][_l2Token][_tokenId] == true,
+            deposits[_localToken][_remoteToken][_tokenId] == true,
             "Token ID is not escrowed in the L1 Bridge"
         );
 
-        deposits[_l1Token][_l2Token][_tokenId] = false;
+        deposits[_localToken][_remoteToken][_tokenId] = false;
 
         // When a withdrawal is finalized on L1, the L1 Bridge transfers the NFT to the withdrawer
         // slither-disable-next-line reentrancy-events
-        IERC721(_l1Token).transferFrom(address(this), _to, _tokenId);
+        IERC721(_localToken).transferFrom(address(this), _to, _tokenId);
 
         // slither-disable-next-line reentrancy-events
-        emit ERC721WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _tokenId, _data);
+        emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
+    }
+
+    /**
+     * @notice Internal function for initiating a token bridge to the other domain.
+     *
+     * @param _localToken  Address of the ERC721 on this domain.
+     * @param _remoteToken Address of the ERC721 on the remote domain.
+     * @param _from        Address of the sender on this domain.
+     * @param _to          Address to receive the token on the other domain.
+     * @param _tokenId     Token ID to bridge.
+     * @param _minGasLimit Minimum gas limit for the bridge message on the other domain.
+     * @param _extraData   Optional data to forward to L2. Data supplied here will not be used to
+     *                     execute any code on L2 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
+     */
+    function _initiateBridgeERC721(
+        address _localToken,
+        address _remoteToken,
+        address _from,
+        address _to,
+        uint256 _tokenId,
+        uint32 _minGasLimit,
+        bytes calldata _extraData
+    ) internal {
+        // Construct calldata for _l2Token.finalizeBridgeERC721(_to, _tokenId)
+        bytes memory message = abi.encodeWithSelector(
+            L2ERC721Bridge.finalizeBridgeERC721.selector,
+            _remoteToken,
+            _localToken,
+            _from,
+            _to,
+            _tokenId,
+            _extraData
+        );
+
+        // Lock token into bridge
+        deposits[_localToken][_remoteToken][_tokenId] = true;
+        IERC721(_localToken).transferFrom(_from, address(this), _tokenId);
+
+        // Send calldata into L2
+        sendCrossDomainMessage(otherBridge, _minGasLimit, message);
+        emit ERC721BridgeInitiated(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
     }
 }
diff --git a/packages/contracts-periphery/contracts/L2/messaging/IL2ERC721Bridge.sol b/packages/contracts-periphery/contracts/L2/messaging/IL2ERC721Bridge.sol
deleted file mode 100644
index 67772c5dbe5845ab35fee793f3a65c4f7423c9df..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/contracts/L2/messaging/IL2ERC721Bridge.sol
+++ /dev/null
@@ -1,108 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
-
-/**
- * @title IL2ERC721Bridge
- */
-interface IL2ERC721Bridge {
-    /**********
-     * Events *
-     **********/
-
-    event ERC721WithdrawalInitiated(
-        address indexed _l1Token,
-        address indexed _l2Token,
-        address indexed _from,
-        address _to,
-        uint256 _tokenId,
-        bytes _data
-    );
-
-    event ERC721DepositFinalized(
-        address indexed _l1Token,
-        address indexed _l2Token,
-        address indexed _from,
-        address _to,
-        uint256 _tokenId,
-        bytes _data
-    );
-
-    event ERC721DepositFailed(
-        address indexed _l1Token,
-        address indexed _l2Token,
-        address indexed _from,
-        address _to,
-        uint256 _tokenId,
-        bytes _data
-    );
-
-    /********************
-     * Public Functions *
-     ********************/
-
-    /**
-     * @dev get the address of the corresponding L1 bridge contract.
-     * @return Address of the corresponding L1 bridge contract.
-     */
-    function l1ERC721Bridge() external returns (address);
-
-    /**
-     * @dev initiate a withdraw of an NFT to the caller's account on L1
-     * @param _l2Token Address of L2 token where withdrawal was initiated.
-     * @param _tokenId Token ID to withdraw.
-     * @param _l1Gas Unused, but included for potential forward compatibility considerations.
-     * @param _data Optional data to forward to L1. This data is provided
-     *        solely as a convenience for external contracts. Aside from enforcing a maximum
-     *        length, these contracts provide no guarantees about its content.
-     */
-    function withdrawERC721(
-        address _l2Token,
-        uint256 _tokenId,
-        uint32 _l1Gas,
-        bytes calldata _data
-    ) external;
-
-    /**
-     * @dev initiate a withdrawal of an NFT to a recipient's account on L1.
-     * @param _l2Token Address of L2 token where withdrawal is initiated.
-     * @param _to L1 adress to send the withdrawal to.
-     * @param _tokenId Token ID to withdraw.
-     * @param _l1Gas Unused, but included for potential forward compatibility considerations.
-     * @param _data Optional data to forward to L1. This data is provided
-     *        solely as a convenience for external contracts. Aside from enforcing a maximum
-     *        length, these contracts provide no guarantees about its content.
-     */
-    function withdrawERC721To(
-        address _l2Token,
-        address _to,
-        uint256 _tokenId,
-        uint32 _l1Gas,
-        bytes calldata _data
-    ) external;
-
-    /*************************
-     * Cross-chain Functions *
-     *************************/
-
-    /**
-     * @dev Complete a deposit from L1 to L2, and send ERC721 token to the recipient on L2.
-     * This call will fail if it did not originate from a corresponding deposit in
-     * L1ERC721Bridge.
-     * @param _l1Token Address for the l1 token this is called with
-     * @param _l2Token Address for the l2 token this is called with
-     * @param _from Account to pull the deposit from on L2.
-     * @param _to Address to receive the withdrawal at
-     * @param _tokenId Token ID to withdraw
-     * @param _data Data provider by the sender on L1. This data is provided
-     *        solely as a convenience for external contracts. Aside from enforcing a maximum
-     *        length, these contracts provide no guarantees about its content.
-     */
-    function finalizeERC721Deposit(
-        address _l1Token,
-        address _l2Token,
-        address _from,
-        address _to,
-        uint256 _tokenId,
-        bytes calldata _data
-    ) external;
-}
diff --git a/packages/contracts-periphery/contracts/L2/messaging/L2ERC721Bridge.sol b/packages/contracts-periphery/contracts/L2/messaging/L2ERC721Bridge.sol
index 5f4cb1db1bdf592b67f189f0659f579d878e7ea5..846554244ae2feabab1f6ccc189e8931cb133072 100644
--- a/packages/contracts-periphery/contracts/L2/messaging/L2ERC721Bridge.sol
+++ b/packages/contracts-periphery/contracts/L2/messaging/L2ERC721Bridge.sol
@@ -1,172 +1,212 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.9;
 
-/* Interface Imports */
-import { IL1ERC721Bridge } from "../../L1/messaging/IL1ERC721Bridge.sol";
-import { IL1ERC721Bridge } from "../../L1/messaging/IL1ERC721Bridge.sol";
-import { IL2ERC721Bridge } from "./IL2ERC721Bridge.sol";
-
-/* Library Imports */
 import {
     CrossDomainEnabled
 } from "@eth-optimism/contracts/contracts/libraries/bridge/CrossDomainEnabled.sol";
+import {
+    OwnableUpgradeable
+} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
 import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
 import { Address } from "@openzeppelin/contracts/utils/Address.sol";
-
-/* Contract Imports */
-import { IL2StandardERC721 } from "../../standards/IL2StandardERC721.sol";
+import { L1ERC721Bridge } from "../../L1/messaging/L1ERC721Bridge.sol";
+import { IOptimismMintableERC721 } from "../../universal/op-erc721/IOptimismMintableERC721.sol";
 
 /**
  * @title L2ERC721Bridge
- * @dev The L2 ERC721 bridge is a contract which works together with the L1 ERC721 bridge to
- * enable ERC721 transitions between L1 and L2.
- * This contract acts as a minter for new tokens when it hears about deposits into the L1 ERC721
- * bridge.
- * This contract also acts as a burner of the token intended for withdrawal, informing the L1
- * bridge to release the L1 NFT.
+ * @notice The L2 ERC721 bridge is a contract which works together with the L1 ERC721 bridge to
+ *         make it possible to transfer ERC721 tokens between Optimism and Ethereum. This contract
+ *         acts as a minter for new tokens when it hears about deposits into the L1 ERC721 bridge.
+ *         This contract also acts as a burner for tokens being withdrawn.
  */
-contract L2ERC721Bridge is IL2ERC721Bridge, CrossDomainEnabled {
-    /********************************
-     * External Contract References *
-     ********************************/
+contract L2ERC721Bridge is CrossDomainEnabled, OwnableUpgradeable {
+    /**
+     * @notice Contract version number.
+     */
+    uint8 public constant VERSION = 1;
 
-    address public l1ERC721Bridge;
+    /**
+     * @notice Emitted when an ERC721 bridge to the other network is initiated.
+     *
+     * @param localToken  Address of the token on this domain.
+     * @param remoteToken Address of the token on the remote domain.
+     * @param from        Address that initiated bridging action.
+     * @param to          Address to receive the token.
+     * @param tokenId     ID of the specific token deposited.
+     * @param extraData   Extra data for use on the client-side.
+     */
+    event ERC721BridgeInitiated(
+        address indexed localToken,
+        address indexed remoteToken,
+        address indexed from,
+        address to,
+        uint256 tokenId,
+        bytes extraData
+    );
 
-    /***************
-     * Constructor *
-     ***************/
+    /**
+     * @notice Emitted when an ERC721 bridge from the other network is finalized.
+     *
+     * @param localToken  Address of the token on this domain.
+     * @param remoteToken Address of the token on the remote domain.
+     * @param from        Address that initiated bridging action.
+     * @param to          Address to receive the token.
+     * @param tokenId     ID of the specific token deposited.
+     * @param extraData   Extra data for use on the client-side.
+     */
+    event ERC721BridgeFinalized(
+        address indexed localToken,
+        address indexed remoteToken,
+        address indexed from,
+        address to,
+        uint256 tokenId,
+        bytes extraData
+    );
 
     /**
-     * @param _l2CrossDomainMessenger Cross-domain messenger used by this contract.
+     * @notice Emitted when an ERC721 bridge from the other network fails.
+     *
+     * @param localToken  Address of the token on this domain.
+     * @param remoteToken Address of the token on the remote domain.
+     * @param from        Address that initiated bridging action.
+     * @param to          Address to receive the token.
+     * @param tokenId     ID of the specific token deposited.
+     * @param extraData   Extra data for use on the client-side.
      */
-    constructor(address _l2CrossDomainMessenger) CrossDomainEnabled(_l2CrossDomainMessenger) {}
+    event ERC721BridgeFailed(
+        address indexed localToken,
+        address indexed remoteToken,
+        address indexed from,
+        address to,
+        uint256 tokenId,
+        bytes extraData
+    );
 
-    /******************
-     * Initialization *
-     ******************/
+    /**
+     * @notice Address of the bridge on the other network.
+     */
+    address public otherBridge;
 
     /**
-     * @param _l1ERC721Bridge Address of the L1 bridge deployed to the main chain.
+     * @param _messenger   Address of the CrossDomainMessenger on this network.
+     * @param _otherBridge Address of the ERC721 bridge on the other network.
      */
-    // slither-disable-next-line external-function
-    function initialize(address _l1ERC721Bridge) public {
-        require(l1ERC721Bridge == address(0), "Contract has already been initialized.");
-        l1ERC721Bridge = _l1ERC721Bridge;
+    constructor(address _messenger, address _otherBridge) CrossDomainEnabled(address(0)) {
+        initialize(_messenger, _otherBridge);
     }
 
-    /***************
-     * Withdrawing *
-     ***************/
-
     /**
-     * @inheritdoc IL2ERC721Bridge
+     * @param _messenger   Address of the CrossDomainMessenger on this network.
+     * @param _otherBridge Address of the ERC721 bridge on the other network.
      */
-    function withdrawERC721(
-        address _l2Token,
-        uint256 _tokenId,
-        uint32 _l1Gas,
-        bytes calldata _data
-    ) external virtual {
-        // Modifier requiring sender to be EOA.  This check could be bypassed by a malicious
-        // contract via initcode, but it takes care of the user error we want to avoid.
-        require(!Address.isContract(msg.sender), "Account not EOA");
+    function initialize(address _messenger, address _otherBridge) public reinitializer(VERSION) {
+        messenger = _messenger;
+        otherBridge = _otherBridge;
 
-        _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _tokenId, _l1Gas, _data);
+        // Initialize upgradable OZ contracts
+        __Ownable_init();
     }
 
     /**
-     * @inheritdoc IL2ERC721Bridge
+     * @notice Initiates a bridge of an NFT to the caller's account on L1.
+     *
+     * @param _localToken  Address of the ERC721 on this domain.
+     * @param _remoteToken Address of the ERC721 on the remote domain.
+     * @param _tokenId     Token ID to bridge.
+     * @param _minGasLimit Minimum gas limit for the bridge message on the other domain.
+     * @param _extraData   Optional data to forward to L1. Data supplied here will not be used to
+     *                     execute any code on L1 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
      */
-    function withdrawERC721To(
-        address _l2Token,
-        address _to,
+    function bridgeERC721(
+        address _localToken,
+        address _remoteToken,
         uint256 _tokenId,
-        uint32 _l1Gas,
-        bytes calldata _data
-    ) external virtual {
-        _initiateWithdrawal(_l2Token, msg.sender, _to, _tokenId, _l1Gas, _data);
+        uint32 _minGasLimit,
+        bytes calldata _extraData
+    ) external {
+        // Modifier requiring sender to be EOA. This check could be bypassed by a malicious
+        // contract via initcode, but it takes care of the user error we want to avoid.
+        require(!Address.isContract(msg.sender), "L2ERC721Bridge: account is not externally owned");
+
+        _initiateBridgeERC721(
+            _localToken,
+            _remoteToken,
+            msg.sender,
+            msg.sender,
+            _tokenId,
+            _minGasLimit,
+            _extraData
+        );
     }
 
     /**
-     * @dev Performs the logic for withdrawals by burning the token and informing
-     *      the L1 token Gateway of the withdrawal.
-     * @param _l2Token Address of L2 token where withdrawal is initiated.
-     * @param _from Account to pull the withdrawal from on L2.
-     * @param _to Account to give the withdrawal to on L1.
-     * @param _tokenId Token ID of the token to withdraw.
-     * @param _l1Gas Unused, but included for potential forward compatibility considerations.
-     * @param _data Optional data to forward to L1. This data is provided
-     *        solely as a convenience for external contracts. Aside from enforcing a maximum
-     *        length, these contracts provide no guarantees about its content.
+     * @notice Initiates a bridge of an NFT to some recipient's account on L1.
+     *
+     * @param _localToken  Address of the ERC721 on this domain.
+     * @param _remoteToken Address of the ERC721 on the remote domain.
+     * @param _to          Address to receive the token on the other domain.
+     * @param _tokenId     Token ID to bridge.
+     * @param _minGasLimit Minimum gas limit for the bridge message on the other domain.
+     * @param _extraData   Optional data to forward to L1. Data supplied here will not be used to
+     *                     execute any code on L1 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
      */
-    function _initiateWithdrawal(
-        address _l2Token,
-        address _from,
+    function bridgeERC721To(
+        address _localToken,
+        address _remoteToken,
         address _to,
         uint256 _tokenId,
-        uint32 _l1Gas,
-        bytes calldata _data
-    ) internal {
-        // Check that the withdrawal is being initiated by the NFT owner
-        require(
-            _from == IL2StandardERC721(_l2Token).ownerOf(_tokenId),
-            "Withdrawal is not being initiated by NFT owner"
-        );
-
-        // When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2
-        // usage
-        // slither-disable-next-line reentrancy-events
-        IL2StandardERC721(_l2Token).burn(_from, _tokenId);
-
-        // Construct calldata for l1ERC721Bridge.finalizeERC721Withdrawal(_to, _tokenId)
-        // slither-disable-next-line reentrancy-events
-        address l1Token = IL2StandardERC721(_l2Token).l1Token();
-        bytes memory message = abi.encodeWithSelector(
-            IL1ERC721Bridge.finalizeERC721Withdrawal.selector,
-            l1Token,
-            _l2Token,
-            _from,
+        uint32 _minGasLimit,
+        bytes calldata _extraData
+    ) external {
+        _initiateBridgeERC721(
+            _localToken,
+            _remoteToken,
+            msg.sender,
             _to,
             _tokenId,
-            _data
+            _minGasLimit,
+            _extraData
         );
-
-        // Send message to L1 bridge
-        // slither-disable-next-line reentrancy-events
-        sendCrossDomainMessage(l1ERC721Bridge, _l1Gas, message);
-
-        // slither-disable-next-line reentrancy-events
-        emit ERC721WithdrawalInitiated(l1Token, _l2Token, _from, _to, _tokenId, _data);
     }
 
-    /************************************
-     * Cross-chain Function: Depositing *
-     ************************************/
-
     /**
-     * @inheritdoc IL2ERC721Bridge
+     * @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the
+     *         recipient on this domain.
+     *
+     * @param _localToken  Address of the ERC721 token on this domain.
+     * @param _remoteToken Address of the ERC721 token on the other domain.
+     * @param _from        Address that triggered the bridge on the other domain.
+     * @param _to          Address to receive the token on this domain.
+     * @param _tokenId     ID of the token being deposited.
+     * @param _extraData   Optional data to forward to L1. Data supplied here will not be used to
+     *                     execute any code on L1 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
      */
-    function finalizeERC721Deposit(
-        address _l1Token,
-        address _l2Token,
+    function finalizeBridgeERC721(
+        address _localToken,
+        address _remoteToken,
         address _from,
         address _to,
         uint256 _tokenId,
-        bytes calldata _data
-    ) external virtual onlyFromCrossDomainAccount(l1ERC721Bridge) {
-        // Check the target token is compliant and
-        // verify the deposited token on L1 matches the L2 deposited token representation here
+        bytes calldata _extraData
+    ) external onlyFromCrossDomainAccount(otherBridge) {
+        // Check the target token is compliant and verify the deposited token on L1 matches the L2
+        // deposited token representation.
         if (
             // slither-disable-next-line reentrancy-events
-            ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
-            _l1Token == IL2StandardERC721(_l2Token).l1Token()
+            ERC165Checker.supportsInterface(
+                _localToken,
+                type(IOptimismMintableERC721).interfaceId
+            ) && _remoteToken == IOptimismMintableERC721(_localToken).remoteToken()
         ) {
             // When a deposit is finalized, we give the NFT with the same tokenId to the account
             // on L2.
             // slither-disable-next-line reentrancy-events
-            IL2StandardERC721(_l2Token).mint(_to, _tokenId);
+            IOptimismMintableERC721(_localToken).mint(_to, _tokenId);
             // slither-disable-next-line reentrancy-events
-            emit ERC721DepositFinalized(_l1Token, _l2Token, _from, _to, _tokenId, _data);
+            emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
         } else {
             // Either the L2 token which is being deposited-into disagrees about the correct address
             // of its L1 token, or does not support the correct interface.
@@ -177,20 +217,80 @@ contract L2ERC721Bridge is IL2ERC721Bridge, CrossDomainEnabled {
             // There is no way to prevent malicious token contracts altogether, but this does limit
             // user error and mitigate some forms of malicious contract behavior.
             bytes memory message = abi.encodeWithSelector(
-                IL1ERC721Bridge.finalizeERC721Withdrawal.selector,
-                _l1Token,
-                _l2Token,
+                L1ERC721Bridge.finalizeBridgeERC721.selector,
+                _remoteToken,
+                _localToken,
                 _to, // switched the _to and _from here to bounce back the deposit to the sender
                 _from,
                 _tokenId,
-                _data
+                _extraData
             );
 
             // Send message up to L1 bridge
             // slither-disable-next-line reentrancy-events
-            sendCrossDomainMessage(l1ERC721Bridge, 0, message);
+            sendCrossDomainMessage(otherBridge, 0, message);
+
             // slither-disable-next-line reentrancy-events
-            emit ERC721DepositFailed(_l1Token, _l2Token, _from, _to, _tokenId, _data);
+            emit ERC721BridgeFailed(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
         }
     }
+
+    /**
+     * @notice Internal function for initiating a token bridge to the other domain.
+     *
+     * @param _localToken  Address of the ERC721 on this domain.
+     * @param _remoteToken Address of the ERC721 on the remote domain.
+     * @param _from        Address of the sender on this domain.
+     * @param _to          Address to receive the token on the other domain.
+     * @param _tokenId     Token ID to bridge.
+     * @param _minGasLimit Minimum gas limit for the bridge message on the other domain.
+     * @param _extraData   Optional data to forward to L1. Data supplied here will not be used to
+     *                     execute any code on L1 and is only emitted as extra data for the
+     *                     convenience of off-chain tooling.
+     */
+    function _initiateBridgeERC721(
+        address _localToken,
+        address _remoteToken,
+        address _from,
+        address _to,
+        uint256 _tokenId,
+        uint32 _minGasLimit,
+        bytes calldata _extraData
+    ) internal {
+        // Check that the withdrawal is being initiated by the NFT owner
+        require(
+            _from == IOptimismMintableERC721(_localToken).ownerOf(_tokenId),
+            "Withdrawal is not being initiated by NFT owner"
+        );
+
+        // When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2
+        // usage
+        // slither-disable-next-line reentrancy-events
+        IOptimismMintableERC721(_localToken).burn(_from, _tokenId);
+
+        // Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId)
+        // slither-disable-next-line reentrancy-events
+        address remoteToken = IOptimismMintableERC721(_localToken).remoteToken();
+        require(
+            remoteToken == _remoteToken,
+            "L2ERC721Bridge: remote token does not match given value"
+        );
+
+        bytes memory message = abi.encodeWithSelector(
+            L1ERC721Bridge.finalizeBridgeERC721.selector,
+            remoteToken,
+            _localToken,
+            _from,
+            _to,
+            _tokenId,
+            _extraData
+        );
+
+        // Send message to L1 bridge
+        // slither-disable-next-line reentrancy-events
+        sendCrossDomainMessage(otherBridge, _minGasLimit, message);
+
+        // slither-disable-next-line reentrancy-events
+        emit ERC721BridgeInitiated(_localToken, remoteToken, _from, _to, _tokenId, _extraData);
+    }
 }
diff --git a/packages/contracts-periphery/contracts/L2/messaging/L2StandardERC721Factory.sol b/packages/contracts-periphery/contracts/L2/messaging/L2StandardERC721Factory.sol
deleted file mode 100644
index d80ef469463a01eeb30d70594689606b3357632b..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/contracts/L2/messaging/L2StandardERC721Factory.sol
+++ /dev/null
@@ -1,63 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
-
-/* Contract Imports */
-import { L2StandardERC721 } from "../../standards/L2StandardERC721.sol";
-
-/**
- * @title L2StandardERC721Factory
- * @dev Factory contract for creating standard L2 ERC721 representations of L1 ERC721s
- * compatible with and working on the NFT bridge.
- */
-contract L2StandardERC721Factory {
-    event StandardL2ERC721Created(address indexed _l1Token, address indexed _l2Token);
-
-    // Address of the L2 ERC721 Bridge.
-    address public l2ERC721Bridge;
-
-    // Maps an L2 ERC721 token address to a boolean that returns true if the token was created
-    // with the L2StandardERC721Factory.
-    mapping(address => bool) public isStandardERC721;
-
-    // Maps an L1 ERC721 to its L2 Standard ERC721 contract, if it exists. This mapping enforces
-    // that there is one, and only one, L2 Standard ERC721 for each L1 ERC721. The purpose of this
-    // is to prevent multiple L2 Standard ERC721s from existing for a single L1 ERC721, which
-    // would result in unnecessary fragmentation, since the Standard ERC721s deployed by this
-    // factory implement the exact same functionality. This mapping should NOT be interpreted as
-    // a token list. This is because a custom L2 ERC721 may be recognized by the community as
-    // the official L2 contract for an L1 ERC721, but the custom contract address wouldn't appear
-    // in this mapping. An off-chain token list will serve as the official source of truth for
-    // L2 ERC721s, similar to Optimism's ERC20 token list:
-    // https://github.com/ethereum-optimism/ethereum-optimism.github.io
-    mapping(address => address) public standardERC721Mapping;
-
-    constructor(address _l2ERC721Bridge) {
-        l2ERC721Bridge = _l2ERC721Bridge;
-    }
-
-    /**
-     * @dev Creates an instance of the standard ERC721 token on L2.
-     * @param _l1Token Address of the corresponding L1 token.
-     * @param _name ERC721 name.
-     * @param _symbol ERC721 symbol.
-     */
-    function createStandardL2ERC721(
-        address _l1Token,
-        string memory _name,
-        string memory _symbol
-    ) external {
-        require(_l1Token != address(0), "Must provide L1 token address");
-
-        // Only one L2 Standard Token can exist for each L1 Token
-        require(
-            standardERC721Mapping[_l1Token] == address(0),
-            "L2 Standard Token already exists for this L1 Token"
-        );
-
-        L2StandardERC721 l2Token = new L2StandardERC721(l2ERC721Bridge, _l1Token, _name, _symbol);
-
-        isStandardERC721[address(l2Token)] = true;
-        standardERC721Mapping[_l1Token] = address(l2Token);
-        emit StandardL2ERC721Created(_l1Token, address(l2Token));
-    }
-}
diff --git a/packages/contracts-periphery/contracts/libraries/utils/Lib_Strings.sol b/packages/contracts-periphery/contracts/libraries/utils/Lib_Strings.sol
deleted file mode 100644
index 05ce0acfeafc6976a05a36cd5a28182e6f5e623c..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/contracts/libraries/utils/Lib_Strings.sol
+++ /dev/null
@@ -1,42 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
-
-/**
- * @title Lib_Strings
- * @dev This library implements a function to convert an address to an ASCII string.
- * It uses the implementation written by tkeber:
- * https://ethereum.stackexchange.com/questions/8346/convert-address-to-string/8447#8447
- */
-library Lib_Strings {
-    /**********************
-     * Internal Functions *
-     **********************/
-
-    /**
-     * Converts an address to its ASCII string representation. The returned string will be
-     * lowercase and the 0x prefix will be removed.
-     * @param _address Address to convert to an ASCII string.
-     * @return String representation of the address.
-     */
-    function addressToString(address _address) internal pure returns (string memory) {
-        bytes memory s = new bytes(40);
-        for (uint256 i = 0; i < 20; i++) {
-            bytes1 b = bytes1(uint8(uint256(uint160(_address)) / (2**(8 * (19 - i)))));
-            bytes1 hi = bytes1(uint8(b) / 16);
-            bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
-            s[2 * i] = hexCharToAscii(hi);
-            s[2 * i + 1] = hexCharToAscii(lo);
-        }
-        return string(s);
-    }
-
-    /**
-     * Converts a hexadecimal character into its ASCII representation.
-     * @param _byte A single hexadecimal character
-     * @return ASCII representation of the hexadecimal character.
-     */
-    function hexCharToAscii(bytes1 _byte) internal pure returns (bytes1) {
-        if (uint8(_byte) < 10) return bytes1(uint8(_byte) + 0x30);
-        else return bytes1(uint8(_byte) + 0x57);
-    }
-}
diff --git a/packages/contracts-periphery/contracts/standards/IL2StandardERC721.sol b/packages/contracts-periphery/contracts/standards/IL2StandardERC721.sol
deleted file mode 100644
index 09973ecc01d54ce685f7170952053682a4dae8d4..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/contracts/standards/IL2StandardERC721.sol
+++ /dev/null
@@ -1,15 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
-
-import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
-
-interface IL2StandardERC721 is IERC721 {
-    function l1Token() external returns (address);
-
-    function mint(address _to, uint256 _tokenId) external;
-
-    function burn(address _from, uint256 _tokenId) external;
-
-    event Mint(address indexed _account, uint256 _tokenId);
-    event Burn(address indexed _account, uint256 _tokenId);
-}
diff --git a/packages/contracts-periphery/contracts/standards/L2StandardERC721.sol b/packages/contracts-periphery/contracts/standards/L2StandardERC721.sol
deleted file mode 100644
index fd5a4b77a5caac633754546ac1ef3b735b897242..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/contracts/standards/L2StandardERC721.sol
+++ /dev/null
@@ -1,80 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
-
-import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
-import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
-import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
-import { Lib_Strings } from "../libraries/utils/Lib_Strings.sol";
-import "./IL2StandardERC721.sol";
-
-contract L2StandardERC721 is IL2StandardERC721, ERC721 {
-    address public l1Token;
-    address public l2Bridge;
-    string public baseTokenURI;
-
-    /**
-     * @param _l2Bridge Address of the L2 standard bridge.
-     * @param _l1Token Address of the corresponding L1 token.
-     * @param _name ERC721 name.
-     * @param _symbol ERC721 symbol.
-     */
-    constructor(
-        address _l2Bridge,
-        address _l1Token,
-        string memory _name,
-        string memory _symbol
-    ) ERC721(_name, _symbol) {
-        l1Token = _l1Token;
-        l2Bridge = _l2Bridge;
-
-        // Creates a base URI in the format specified by EIP-681:
-        // https://eips.ethereum.org/EIPS/eip-681
-        baseTokenURI = string(
-            abi.encodePacked(
-                "ethereum:0x",
-                Lib_Strings.addressToString(_l1Token),
-                "@",
-                Strings.toString(block.chainid),
-                "/tokenURI?uint256="
-            )
-        );
-    }
-
-    modifier onlyL2Bridge() {
-        require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");
-        _;
-    }
-
-    // slither-disable-next-line external-function
-    function supportsInterface(bytes4 _interfaceId)
-        public
-        view
-        override(ERC721, IERC165)
-        returns (bool)
-    {
-        bytes4 iface1 = type(IERC165).interfaceId;
-        bytes4 iface2 = type(IL2StandardERC721).interfaceId;
-        return
-            _interfaceId == iface1 ||
-            _interfaceId == iface2 ||
-            super.supportsInterface(_interfaceId);
-    }
-
-    // slither-disable-next-line external-function
-    function mint(address _to, uint256 _tokenId) public virtual onlyL2Bridge {
-        _mint(_to, _tokenId);
-
-        emit Mint(_to, _tokenId);
-    }
-
-    // slither-disable-next-line external-function
-    function burn(address _from, uint256 _tokenId) public virtual onlyL2Bridge {
-        _burn(_tokenId);
-
-        emit Burn(_from, _tokenId);
-    }
-
-    function _baseURI() internal view virtual override returns (string memory) {
-        return baseTokenURI;
-    }
-}
diff --git a/packages/contracts-periphery/contracts/test-libraries/utils/TestLib_Strings.sol b/packages/contracts-periphery/contracts/test-libraries/utils/TestLib_Strings.sol
deleted file mode 100644
index 520093b3b759d21a17610fc113a233097639410c..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/contracts/test-libraries/utils/TestLib_Strings.sol
+++ /dev/null
@@ -1,18 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.9;
-
-/* Library Imports */
-import { Lib_Strings } from "../../libraries/utils/Lib_Strings.sol";
-
-/**
- * @title TestLib_Strings
- */
-contract TestLib_Strings {
-    function addressToString(address _address) public pure returns (string memory) {
-        return Lib_Strings.addressToString(_address);
-    }
-
-    function hexCharToAscii(bytes1 _byte) public pure returns (bytes1) {
-        return Lib_Strings.hexCharToAscii(_byte);
-    }
-}
diff --git a/packages/contracts-periphery/contracts/universal/op-erc721/IOptimismMintableERC721.sol b/packages/contracts-periphery/contracts/universal/op-erc721/IOptimismMintableERC721.sol
new file mode 100644
index 0000000000000000000000000000000000000000..bab4e56865ec9f7f19793aee16a372818827aee2
--- /dev/null
+++ b/packages/contracts-periphery/contracts/universal/op-erc721/IOptimismMintableERC721.sol
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.9;
+
+import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
+
+/**
+ * @title IOptimismMintableERC721
+ * @notice Interface for contracts that are compatible with the OptimismMintableERC721 standard.
+ *         Tokens that follow this standard can be easily transferred across the ERC721 bridge.
+ */
+interface IOptimismMintableERC721 is IERC721 {
+    /**
+     * @notice Emitted when a token is minted.
+     *
+     * @param account Address of the account the token was minted to.
+     * @param tokenId Token ID of the minted token.
+     */
+    event Mint(address indexed account, uint256 tokenId);
+
+    /**
+     * @notice Emitted when a token is burned.
+     *
+     * @param account Address of the account the token was burned from.
+     * @param tokenId Token ID of the burned token.
+     */
+    event Burn(address indexed account, uint256 tokenId);
+
+    /**
+     * @notice Address of the token on the remote domain.
+     */
+    function remoteToken() external returns (address);
+
+    /**
+     * @notice Address of the ERC721 bridge on this network.
+     */
+    function bridge() external returns (address);
+
+    /**
+     * @notice Mints some token ID for a user.
+     *
+     * @param _to      Address of the user to mint the token for.
+     * @param _tokenId Token ID to mint.
+     */
+    function mint(address _to, uint256 _tokenId) external;
+
+    /**
+     * @notice Burns a token ID from a user.
+     *
+     * @param _from    Address of the user to burn the token from.
+     * @param _tokenId Token ID to burn.
+     */
+    function burn(address _from, uint256 _tokenId) external;
+}
diff --git a/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721.sol b/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721.sol
new file mode 100644
index 0000000000000000000000000000000000000000..3ecdf6b503cba4a63cc06bdcc6c6f06200d7571a
--- /dev/null
+++ b/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721.sol
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.9;
+
+import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
+import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
+import { IOptimismMintableERC721 } from "./IOptimismMintableERC721.sol";
+
+/**
+ * @title OptimismMintableERC721
+ * @notice This contract is the remote representation for some token that lives on another network,
+ *         typically an Optimism representation of an Ethereum-based token. Standard reference
+ *         implementation that can be extended or modified according to your needs.
+ */
+contract OptimismMintableERC721 is ERC721, IOptimismMintableERC721 {
+    /**
+     * @inheritdoc IOptimismMintableERC721
+     */
+    address public remoteToken;
+
+    /**
+     * @inheritdoc IOptimismMintableERC721
+     */
+    address public bridge;
+
+    /**
+     * @notice Base token URI for this token.
+     */
+    string public baseTokenURI;
+
+    /**
+     * @param _bridge      Address of the bridge on this network.
+     * @param _remoteToken Address of the corresponding token on the other network.
+     * @param _name        ERC721 name.
+     * @param _symbol      ERC721 symbol.
+     */
+    constructor(
+        address _bridge,
+        address _remoteToken,
+        string memory _name,
+        string memory _symbol
+    ) ERC721(_name, _symbol) {
+        remoteToken = _remoteToken;
+        bridge = _bridge;
+
+        // Creates a base URI in the format specified by EIP-681:
+        // https://eips.ethereum.org/EIPS/eip-681
+        baseTokenURI = string(
+            abi.encodePacked(
+                "ethereum:",
+                Strings.toHexString(uint160(_remoteToken)),
+                "@",
+                Strings.toString(block.chainid),
+                "/tokenURI?uint256="
+            )
+        );
+    }
+
+    /**
+     * @notice Modifier that prevents callers other than the bridge from calling the function.
+     */
+    modifier onlyBridge() {
+        require(msg.sender == bridge, "OptimismMintableERC721: only bridge can call this function");
+        _;
+    }
+
+    /**
+     * @inheritdoc IOptimismMintableERC721
+     */
+    function mint(address _to, uint256 _tokenId) external virtual onlyBridge {
+        _mint(_to, _tokenId);
+
+        emit Mint(_to, _tokenId);
+    }
+
+    /**
+     * @inheritdoc IOptimismMintableERC721
+     */
+    function burn(address _from, uint256 _tokenId) external virtual onlyBridge {
+        _burn(_tokenId);
+
+        emit Burn(_from, _tokenId);
+    }
+
+    /**
+     * @notice Checks if a given interface ID is supported by this contract.
+     *
+     * @param _interfaceId The interface ID to check.
+     *
+     * @return True if the interface ID is supported, false otherwise.
+     */
+    function supportsInterface(bytes4 _interfaceId)
+        public
+        view
+        override(ERC721, IERC165)
+        returns (bool)
+    {
+        bytes4 iface1 = type(IERC165).interfaceId;
+        bytes4 iface2 = type(IOptimismMintableERC721).interfaceId;
+        return
+            _interfaceId == iface1 ||
+            _interfaceId == iface2 ||
+            super.supportsInterface(_interfaceId);
+    }
+
+    /**
+     * @notice Returns the base token URI.
+     *
+     * @return Base token URI.
+     */
+    function _baseURI() internal view virtual override returns (string memory) {
+        return baseTokenURI;
+    }
+}
diff --git a/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol b/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol
new file mode 100644
index 0000000000000000000000000000000000000000..bf4365c384a1ca8e2f7d1df22f0b62625f5cb969
--- /dev/null
+++ b/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.9;
+
+import {
+    OwnableUpgradeable
+} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
+import { OptimismMintableERC721 } from "./OptimismMintableERC721.sol";
+
+/**
+ * @title OptimismMintableERC721Factory
+ * @notice Factory contract for creating OptimismMintableERC721 contracts.
+ */
+contract OptimismMintableERC721Factory is OwnableUpgradeable {
+    /**
+     * @notice Contract version number.
+     */
+    uint8 public constant VERSION = 1;
+
+    /**
+     * @notice Emitted whenever a new OptimismMintableERC721 contract is created.
+     *
+     * @param remoteToken Address of the token on the remote domain.
+     * @param localToken  Address of the token on the this domain.
+     */
+    event OptimismMintableERC721Created(address indexed remoteToken, address indexed localToken);
+
+    /**
+     * @notice Address of the ERC721 bridge on this network.
+     */
+    address public bridge;
+
+    /**
+     * @notice Tracks addresses created by this factory.
+     */
+    mapping(address => bool) public isStandardOptimismMintableERC721;
+
+    /**
+     * @param _bridge Address of the ERC721 bridge on this network.
+     */
+    constructor(address _bridge) {
+        intialize(_bridge);
+    }
+
+    /**
+     * @notice Initializes the factory.
+     *
+     * @param _bridge Address of the ERC721 bridge on this network.
+     */
+    function intialize(address _bridge) public reinitializer(VERSION) {
+        bridge = _bridge;
+
+        // Initialize upgradable OZ contracts
+        __Ownable_init();
+    }
+
+    /**
+     * @notice Creates an instance of the standard ERC721.
+     *
+     * @param _remoteToken Address of the corresponding token on the other domain.
+     * @param _name ERC721 name.
+     * @param _symbol ERC721 symbol.
+     */
+    function createStandardOptimismMintableERC721(
+        address _remoteToken,
+        string memory _name,
+        string memory _symbol
+    ) external {
+        require(
+            _remoteToken != address(0),
+            "OptimismMintableERC721Factory: L1 token address cannot be address(0)"
+        );
+        require(
+            bridge != address(0),
+            "OptimismMintableERC721Factory: bridge address must be initialized"
+        );
+
+        OptimismMintableERC721 localToken = new OptimismMintableERC721(
+            bridge,
+            _remoteToken,
+            _name,
+            _symbol
+        );
+
+        isStandardOptimismMintableERC721[address(localToken)] = true;
+        emit OptimismMintableERC721Created(_remoteToken, address(localToken));
+    }
+}
diff --git a/packages/contracts-periphery/package.json b/packages/contracts-periphery/package.json
index bdc9be415d18570551d4370b7787f27c191a114b..450fb98cea5dcac1e3a22efaa00013a8072811cd 100644
--- a/packages/contracts-periphery/package.json
+++ b/packages/contracts-periphery/package.json
@@ -62,6 +62,7 @@
     "@nomiclabs/hardhat-waffle": "^2.0.1",
     "@rari-capital/solmate": "^6.3.0",
     "@openzeppelin/contracts": "4.6.0",
+    "@openzeppelin/contracts-upgradeable": "4.6.0",
     "@types/chai": "^4.2.18",
     "@types/mocha": "^8.2.2",
     "@types/node": "^17.0.21",
diff --git a/packages/contracts-periphery/test/contracts/L1/messaging/L1ERC721Bridge.spec.ts b/packages/contracts-periphery/test/contracts/L1/messaging/L1ERC721Bridge.spec.ts
index 6fac674609e0b645c6d52d0a753501b6a2b51edc..e785804a747de9f8e9ee01a8386e1e173951f892 100644
--- a/packages/contracts-periphery/test/contracts/L1/messaging/L1ERC721Bridge.spec.ts
+++ b/packages/contracts-periphery/test/contracts/L1/messaging/L1ERC721Bridge.spec.ts
@@ -89,11 +89,11 @@ describe('L1ERC721Bridge', () => {
       await L1ERC721.connect(alice).approve(L1ERC721Bridge.address, tokenId)
     })
 
-    it('depositERC721() escrows the deposit and sends the correct deposit message', async () => {
+    it('bridgeERC721() escrows the deposit and sends the correct deposit message', async () => {
       // alice calls deposit on the bridge and the L1 bridge calls transferFrom on the token.
-      // emits an ERC721DepositInitiated event with the correct arguments.
+      // emits an ERC721BridgeInitiated event with the correct arguments.
       await expect(
-        L1ERC721Bridge.connect(alice).depositERC721(
+        L1ERC721Bridge.connect(alice).bridgeERC721(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
           tokenId,
@@ -101,7 +101,7 @@ describe('L1ERC721Bridge', () => {
           NON_NULL_BYTES32
         )
       )
-        .to.emit(L1ERC721Bridge, 'ERC721DepositInitiated')
+        .to.emit(L1ERC721Bridge, 'ERC721BridgeInitiated')
         .withArgs(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
@@ -129,9 +129,9 @@ describe('L1ERC721Bridge', () => {
 
       // the L1 bridge sends the correct message to the L1 messenger
       expect(depositCallToMessenger.args[1]).to.equal(
-        IL2ERC721Bridge.encodeFunctionData('finalizeERC721Deposit', [
-          L1ERC721.address,
+        IL2ERC721Bridge.encodeFunctionData('finalizeBridgeERC721', [
           DUMMY_L2_ERC721_ADDRESS,
+          L1ERC721.address,
           aliceAddress,
           aliceAddress,
           tokenId,
@@ -150,11 +150,11 @@ describe('L1ERC721Bridge', () => {
       ).to.equal(true)
     })
 
-    it('depositERC721To() escrows the deposited NFT and sends the correct deposit message', async () => {
+    it('bridgeERC721To() escrows the deposited NFT and sends the correct deposit message', async () => {
       // depositor calls deposit on the bridge and the L1 bridge calls transferFrom on the token.
-      // emits an ERC721DepositInitiated event with the correct arguments.
+      // emits an ERC721BridgeInitiated event with the correct arguments.
       await expect(
-        L1ERC721Bridge.connect(alice).depositERC721To(
+        L1ERC721Bridge.connect(alice).bridgeERC721To(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
           bobsAddress,
@@ -163,7 +163,7 @@ describe('L1ERC721Bridge', () => {
           NON_NULL_BYTES32
         )
       )
-        .to.emit(L1ERC721Bridge, 'ERC721DepositInitiated')
+        .to.emit(L1ERC721Bridge, 'ERC721BridgeInitiated')
         .withArgs(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
@@ -195,9 +195,9 @@ describe('L1ERC721Bridge', () => {
 
       // the L1 bridge sends the correct message to the L1 messenger
       expect(depositCallToMessenger.args[1]).to.equal(
-        IL2ERC721Bridge.encodeFunctionData('finalizeERC721Deposit', [
-          L1ERC721.address,
+        IL2ERC721Bridge.encodeFunctionData('finalizeBridgeERC721', [
           DUMMY_L2_ERC721_ADDRESS,
+          L1ERC721.address,
           aliceAddress,
           bobsAddress,
           tokenId,
@@ -216,22 +216,22 @@ describe('L1ERC721Bridge', () => {
       ).to.equal(true)
     })
 
-    it('cannot depositERC721 from a contract account', async () => {
+    it('cannot bridgeERC721 from a contract account', async () => {
       await expect(
-        L1ERC721Bridge.depositERC721(
+        L1ERC721Bridge.bridgeERC721(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
           tokenId,
           FINALIZATION_GAS,
           NON_NULL_BYTES32
         )
-      ).to.be.revertedWith('Account not EOA')
+      ).to.be.revertedWith('L1ERC721Bridge: account is not externally owned')
     })
 
     describe('Handling ERC721.transferFrom() failures that revert', () => {
-      it('depositERC721(): will revert if ERC721.transferFrom() reverts', async () => {
+      it('bridgeERC721(): will revert if ERC721.transferFrom() reverts', async () => {
         await expect(
-          L1ERC721Bridge.connect(bob).depositERC721To(
+          L1ERC721Bridge.connect(bob).bridgeERC721To(
             L1ERC721.address,
             DUMMY_L2_ERC721_ADDRESS,
             bobsAddress,
@@ -242,9 +242,9 @@ describe('L1ERC721Bridge', () => {
         ).to.be.revertedWith('ERC721: transfer from incorrect owner')
       })
 
-      it('depositERC721To(): will revert if ERC721.transferFrom() reverts', async () => {
+      it('bridgeERC721To(): will revert if ERC721.transferFrom() reverts', async () => {
         await expect(
-          L1ERC721Bridge.connect(bob).depositERC721To(
+          L1ERC721Bridge.connect(bob).bridgeERC721To(
             L1ERC721.address,
             DUMMY_L2_ERC721_ADDRESS,
             bobsAddress,
@@ -255,9 +255,9 @@ describe('L1ERC721Bridge', () => {
         ).to.be.revertedWith('ERC721: transfer from incorrect owner')
       })
 
-      it('depositERC721To(): will revert if the L1 ERC721 is zero address', async () => {
+      it('bridgeERC721To(): will revert if the L1 ERC721 is zero address', async () => {
         await expect(
-          L1ERC721Bridge.connect(alice).depositERC721To(
+          L1ERC721Bridge.connect(alice).bridgeERC721To(
             constants.AddressZero,
             DUMMY_L2_ERC721_ADDRESS,
             bobsAddress,
@@ -268,9 +268,9 @@ describe('L1ERC721Bridge', () => {
         ).to.be.revertedWith('function call to a non-contract account')
       })
 
-      it('depositERC721To(): will revert if the L1 ERC721 has no code', async () => {
+      it('bridgeERC721To(): will revert if the L1 ERC721 has no code', async () => {
         await expect(
-          L1ERC721Bridge.connect(alice).depositERC721To(
+          L1ERC721Bridge.connect(alice).bridgeERC721To(
             bobsAddress,
             DUMMY_L2_ERC721_ADDRESS,
             bobsAddress,
@@ -286,7 +286,7 @@ describe('L1ERC721Bridge', () => {
   describe('ERC721 withdrawals', () => {
     it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L1 account', async () => {
       await expect(
-        L1ERC721Bridge.connect(alice).finalizeERC721Withdrawal(
+        L1ERC721Bridge.connect(alice).finalizeBridgeERC721(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
           constants.AddressZero,
@@ -299,7 +299,7 @@ describe('L1ERC721Bridge', () => {
 
     it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L2DepositedERC721)', async () => {
       await expect(
-        L1ERC721Bridge.finalizeERC721Withdrawal(
+        L1ERC721Bridge.finalizeBridgeERC721(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
           constants.AddressZero,
@@ -318,7 +318,7 @@ describe('L1ERC721Bridge', () => {
         // First Alice will send an NFT so that there's a balance to be withdrawn
         await L1ERC721.connect(alice).approve(L1ERC721Bridge.address, tokenId)
 
-        await L1ERC721Bridge.connect(alice).depositERC721(
+        await L1ERC721Bridge.connect(alice).bridgeERC721(
           L1ERC721.address,
           DUMMY_L2_ERC721_ADDRESS,
           tokenId,
@@ -336,7 +336,7 @@ describe('L1ERC721Bridge', () => {
 
       it('should revert if the l1/l2 token pair has a token ID that has not been escrowed in the l1 bridge', async () => {
         await expect(
-          L1ERC721Bridge.finalizeERC721Withdrawal(
+          L1ERC721Bridge.finalizeBridgeERC721(
             L1ERC721.address,
             DUMMY_L2_BRIDGE_ADDRESS, // incorrect l2 token address
             constants.AddressZero,
@@ -351,9 +351,9 @@ describe('L1ERC721Bridge', () => {
       })
 
       it('should credit funds to the withdrawer and not use too much gas', async () => {
-        // finalizing the withdrawal emits an ERC721DepositInitiated event with the correct arguments.
+        // finalizing the withdrawal emits an ERC721BridgeFinalized event with the correct arguments.
         await expect(
-          L1ERC721Bridge.finalizeERC721Withdrawal(
+          L1ERC721Bridge.finalizeBridgeERC721(
             L1ERC721.address,
             DUMMY_L2_ERC721_ADDRESS,
             NON_ZERO_ADDRESS,
@@ -363,7 +363,7 @@ describe('L1ERC721Bridge', () => {
             { from: Fake__L1CrossDomainMessenger.address }
           )
         )
-          .to.emit(L1ERC721Bridge, 'ERC721WithdrawalFinalized')
+          .to.emit(L1ERC721Bridge, 'ERC721BridgeFinalized')
           .withArgs(
             L1ERC721.address,
             DUMMY_L2_ERC721_ADDRESS,
diff --git a/packages/contracts-periphery/test/contracts/L2/messaging/L2ERC721Bridge.spec.ts b/packages/contracts-periphery/test/contracts/L2/messaging/L2ERC721Bridge.spec.ts
index b556bb19ff2c45bb1e411526f0bad313cee24f39..f9b4d2fbace68e538a46bc7f6b48d5febcd62d95 100644
--- a/packages/contracts-periphery/test/contracts/L2/messaging/L2ERC721Bridge.spec.ts
+++ b/packages/contracts-periphery/test/contracts/L2/messaging/L2ERC721Bridge.spec.ts
@@ -10,7 +10,7 @@ import {
   NON_ZERO_ADDRESS,
 } from '../../../../../contracts/test/helpers'
 
-const ERR_ALREADY_INITIALIZED = 'Contract has already been initialized.'
+const ERR_ALREADY_INITIALIZED = 'Initializable: contract is already initialized'
 const ERR_INVALID_MESSENGER = 'OVM_XCHAIN: messenger contract unauthenticated'
 const ERR_INVALID_X_DOMAIN_MSG_SENDER =
   'OVM_XCHAIN: wrong sender of cross-domain message'
@@ -53,14 +53,11 @@ describe('L2ERC721Bridge', () => {
     // Deploy the contract under test
     L2ERC721Bridge = await (
       await ethers.getContractFactory('L2ERC721Bridge')
-    ).deploy(Fake__L2CrossDomainMessenger.address)
-
-    // Initialize the contract
-    await L2ERC721Bridge.initialize(DUMMY_L1BRIDGE_ADDRESS)
+    ).deploy(Fake__L2CrossDomainMessenger.address, DUMMY_L1BRIDGE_ADDRESS)
 
     // Deploy an L2 ERC721
     L2ERC721 = await (
-      await ethers.getContractFactory('L2StandardERC721')
+      await ethers.getContractFactory('OptimismMintableERC721')
     ).deploy(
       L2ERC721Bridge.address,
       DUMMY_L1ERC721_ADDRESS,
@@ -73,16 +70,19 @@ describe('L2ERC721Bridge', () => {
   describe('initialize', () => {
     it('Should only be callable once', async () => {
       await expect(
-        L2ERC721Bridge.initialize(DUMMY_L1BRIDGE_ADDRESS)
+        L2ERC721Bridge.initialize(
+          Fake__L2CrossDomainMessenger.address,
+          DUMMY_L1BRIDGE_ADDRESS
+        )
       ).to.be.revertedWith(ERR_ALREADY_INITIALIZED)
     })
   })
 
   // test the transfer flow of moving a token from L1 to L2
-  describe('finalizeERC721Deposit', () => {
+  describe('finalizeBridgeERC721', () => {
     it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L2 account', async () => {
       await expect(
-        L2ERC721Bridge.connect(alice).finalizeERC721Deposit(
+        L2ERC721Bridge.connect(alice).finalizeBridgeERC721(
           DUMMY_L1ERC721_ADDRESS,
           NON_ZERO_ADDRESS,
           NON_ZERO_ADDRESS,
@@ -99,7 +99,7 @@ describe('L2ERC721Bridge', () => {
       )
 
       await expect(
-        L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeERC721Deposit(
+        L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeBridgeERC721(
           DUMMY_L1ERC721_ADDRESS,
           NON_ZERO_ADDRESS,
           NON_ZERO_ADDRESS,
@@ -125,11 +125,11 @@ describe('L2ERC721Bridge', () => {
         DUMMY_L1BRIDGE_ADDRESS
       )
 
-      // A failed attempt to finalize the deposit causes an ERC721DepositFailed event to be emitted.
+      // A failed attempt to finalize the deposit causes an ERC721BridgeFailed event to be emitted.
       await expect(
-        L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeERC721Deposit(
-          DUMMY_L1ERC721_ADDRESS,
+        L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeBridgeERC721(
           NonCompliantERC721.address,
+          DUMMY_L1ERC721_ADDRESS,
           aliceAddress,
           bobsAddress,
           TOKEN_ID,
@@ -139,10 +139,10 @@ describe('L2ERC721Bridge', () => {
           }
         )
       )
-        .to.emit(L2ERC721Bridge, 'ERC721DepositFailed')
+        .to.emit(L2ERC721Bridge, 'ERC721BridgeFailed')
         .withArgs(
-          DUMMY_L1ERC721_ADDRESS,
           NonCompliantERC721.address,
+          DUMMY_L1ERC721_ADDRESS,
           aliceAddress,
           bobsAddress,
           TOKEN_ID,
@@ -155,7 +155,7 @@ describe('L2ERC721Bridge', () => {
       expect(withdrawalCallToMessenger.args[0]).to.equal(DUMMY_L1BRIDGE_ADDRESS)
       expect(withdrawalCallToMessenger.args[1]).to.equal(
         Factory__L1ERC721Bridge.interface.encodeFunctionData(
-          'finalizeERC721Withdrawal',
+          'finalizeBridgeERC721',
           [
             DUMMY_L1ERC721_ADDRESS,
             NonCompliantERC721.address,
@@ -181,9 +181,9 @@ describe('L2ERC721Bridge', () => {
 
       // Successfully finalizes the deposit.
       const expectedResult = expect(
-        L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeERC721Deposit(
-          DUMMY_L1ERC721_ADDRESS,
+        L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeBridgeERC721(
           L2ERC721.address,
+          DUMMY_L1ERC721_ADDRESS,
           aliceAddress,
           bobsAddress,
           TOKEN_ID,
@@ -194,12 +194,12 @@ describe('L2ERC721Bridge', () => {
         )
       )
 
-      // Depositing causes an ERC721DepositFinalized event to be emitted.
+      // Depositing causes an ERC721BridgeFinalized event to be emitted.
       await expectedResult.to
-        .emit(L2ERC721Bridge, 'ERC721DepositFinalized')
+        .emit(L2ERC721Bridge, 'ERC721BridgeFinalized')
         .withArgs(
-          DUMMY_L1ERC721_ADDRESS,
           L2ERC721.address,
+          DUMMY_L1ERC721_ADDRESS,
           aliceAddress,
           bobsAddress,
           TOKEN_ID,
@@ -222,7 +222,7 @@ describe('L2ERC721Bridge', () => {
 
     beforeEach(async () => {
       Mock__L2Token = await (
-        await smock.mock('L2StandardERC721')
+        await smock.mock('OptimismMintableERC721')
       ).deploy(
         L2ERC721Bridge.address,
         DUMMY_L1ERC721_ADDRESS,
@@ -239,10 +239,11 @@ describe('L2ERC721Bridge', () => {
       })
     })
 
-    it('withdrawERC721() reverts when called by non-owner of nft', async () => {
+    it('bridgeERC721() reverts when called by non-owner of nft', async () => {
       await expect(
-        L2ERC721Bridge.connect(bob).withdrawERC721(
+        L2ERC721Bridge.connect(bob).bridgeERC721(
           Mock__L2Token.address,
+          DUMMY_L1ERC721_ADDRESS,
           TOKEN_ID,
           0,
           NON_NULL_BYTES32
@@ -250,37 +251,39 @@ describe('L2ERC721Bridge', () => {
       ).to.be.revertedWith(ERR_INVALID_WITHDRAWAL)
     })
 
-    it('withdrawERC721() reverts if called by a contract', async () => {
+    it('bridgeERC721() reverts if called by a contract', async () => {
       await expect(
-        L2ERC721Bridge.connect(l2MessengerImpersonator).withdrawERC721(
+        L2ERC721Bridge.connect(l2MessengerImpersonator).bridgeERC721(
           Mock__L2Token.address,
+          DUMMY_L1ERC721_ADDRESS,
           TOKEN_ID,
           0,
           NON_NULL_BYTES32
         )
-      ).to.be.revertedWith('Account not EOA')
+      ).to.be.revertedWith('L2ERC721Bridge: account is not externally owned')
     })
 
-    it('withdrawERC721() burns and sends the correct withdrawal message', async () => {
+    it('bridgeERC721() burns and sends the correct withdrawal message', async () => {
       // Make sure that alice begins as the NFT owner
       expect(await Mock__L2Token.ownerOf(TOKEN_ID)).to.equal(aliceAddress)
 
       // Initiates a successful withdrawal.
       const expectedResult = expect(
-        L2ERC721Bridge.connect(alice).withdrawERC721(
+        L2ERC721Bridge.connect(alice).bridgeERC721(
           Mock__L2Token.address,
+          DUMMY_L1ERC721_ADDRESS,
           TOKEN_ID,
           0,
           NON_NULL_BYTES32
         )
       )
 
-      // A successful withdrawal causes an ERC721WithdrawalInitiated event to be emitted from the L2 ERC721 Bridge.
+      // A successful withdrawal causes an ERC721BridgeInitiated event to be emitted from the L2 ERC721 Bridge.
       await expectedResult.to
-        .emit(L2ERC721Bridge, 'ERC721WithdrawalInitiated')
+        .emit(L2ERC721Bridge, 'ERC721BridgeInitiated')
         .withArgs(
-          DUMMY_L1ERC721_ADDRESS,
           Mock__L2Token.address,
+          DUMMY_L1ERC721_ADDRESS,
           aliceAddress,
           aliceAddress,
           TOKEN_ID,
@@ -311,7 +314,7 @@ describe('L2ERC721Bridge', () => {
       // Message data should be a call telling the L1ERC721Bridge to finalize the withdrawal
       expect(withdrawalCallToMessenger.args[1]).to.equal(
         Factory__L1ERC721Bridge.interface.encodeFunctionData(
-          'finalizeERC721Withdrawal',
+          'finalizeBridgeERC721',
           [
             DUMMY_L1ERC721_ADDRESS,
             Mock__L2Token.address,
@@ -326,10 +329,11 @@ describe('L2ERC721Bridge', () => {
       expect(withdrawalCallToMessenger.args[2]).to.equal(0)
     })
 
-    it('withdrawERC721To() reverts when called by non-owner of nft', async () => {
+    it('bridgeERC721To() reverts when called by non-owner of nft', async () => {
       await expect(
-        L2ERC721Bridge.connect(bob).withdrawERC721To(
+        L2ERC721Bridge.connect(bob).bridgeERC721To(
           Mock__L2Token.address,
+          DUMMY_L1ERC721_ADDRESS,
           bobsAddress,
           TOKEN_ID,
           0,
@@ -338,14 +342,15 @@ describe('L2ERC721Bridge', () => {
       ).to.be.revertedWith(ERR_INVALID_WITHDRAWAL)
     })
 
-    it('withdrawERC721To() burns and sends the correct withdrawal message', async () => {
+    it('bridgeERC721To() burns and sends the correct withdrawal message', async () => {
       // Make sure that alice begins as the NFT owner
       expect(await Mock__L2Token.ownerOf(TOKEN_ID)).to.equal(aliceAddress)
 
       // Initiates a successful withdrawal.
       const expectedResult = expect(
-        L2ERC721Bridge.connect(alice).withdrawERC721To(
+        L2ERC721Bridge.connect(alice).bridgeERC721To(
           Mock__L2Token.address,
+          DUMMY_L1ERC721_ADDRESS,
           bobsAddress,
           TOKEN_ID,
           0,
@@ -353,12 +358,12 @@ describe('L2ERC721Bridge', () => {
         )
       )
 
-      // A successful withdrawal causes an ERC721WithdrawalInitiated event to be emitted from the L2 ERC721 Bridge.
+      // A successful withdrawal causes an ERC721BridgeInitiated event to be emitted from the L2 ERC721 Bridge.
       await expectedResult.to
-        .emit(L2ERC721Bridge, 'ERC721WithdrawalInitiated')
+        .emit(L2ERC721Bridge, 'ERC721BridgeInitiated')
         .withArgs(
-          DUMMY_L1ERC721_ADDRESS,
           Mock__L2Token.address,
+          DUMMY_L1ERC721_ADDRESS,
           aliceAddress,
           bobsAddress,
           TOKEN_ID,
@@ -389,7 +394,7 @@ describe('L2ERC721Bridge', () => {
       // The message data should be a call telling the L1ERC721Bridge to finalize the withdrawal
       expect(withdrawalCallToMessenger.args[1]).to.equal(
         Factory__L1ERC721Bridge.interface.encodeFunctionData(
-          'finalizeERC721Withdrawal',
+          'finalizeBridgeERC721',
           [
             DUMMY_L1ERC721_ADDRESS,
             Mock__L2Token.address,
diff --git a/packages/contracts-periphery/test/contracts/libraries/utils/Lib_Strings.spec.ts b/packages/contracts-periphery/test/contracts/libraries/utils/Lib_Strings.spec.ts
deleted file mode 100644
index 6d8b9dbc0720478e70da6490c223119ebdacc0f0..0000000000000000000000000000000000000000
--- a/packages/contracts-periphery/test/contracts/libraries/utils/Lib_Strings.spec.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { ethers } from 'hardhat'
-import { Contract } from 'ethers'
-
-import { expect } from '../../../setup'
-import { deploy } from '../../../helpers'
-
-const DUMMY_ADDRESS = ethers.utils.getAddress('0x' + 'abba'.repeat(10))
-
-describe('Lib_Strings', () => {
-  let TestLib_Strings: Contract
-  before(async () => {
-    TestLib_Strings = await deploy('TestLib_Strings')
-  })
-
-  describe('addressToString', () => {
-    it('should return a string type', () => {
-      // uses the contract interface to find the function's return type
-      const returnType =
-        TestLib_Strings.interface.functions['addressToString(address)']
-          .outputs[0].type
-
-      expect(returnType).to.equal('string')
-    })
-
-    it('should convert an address to a lowercase ascii string without the 0x prefix', async () => {
-      const asciiString = DUMMY_ADDRESS.substring(2).toLowerCase()
-
-      expect(await TestLib_Strings.addressToString(DUMMY_ADDRESS)).to.equal(
-        asciiString
-      )
-    })
-  })
-
-  describe('hexCharToAscii', () => {
-    for (let hex = 0; hex < 16; hex++) {
-      it(`should convert the hex character ${hex} to its ascii representation`, async () => {
-        // converts hex characters to ascii in decimal representation
-        const asciiDecimal =
-          hex < 10
-            ? hex + 48 // 48 is 0x30 in decimal
-            : hex + 87 // 87 is 0x57 in decimal
-
-        // converts decimal value to hexadecimal and prepends '0x'
-        const asciiHexadecimal = '0x' + asciiDecimal.toString(16)
-
-        expect(await TestLib_Strings.hexCharToAscii(hex)).to.equal(
-          asciiHexadecimal
-        )
-      })
-    }
-  })
-})
diff --git a/packages/contracts-periphery/test/contracts/standards/L2StandardERC721.spec.ts b/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721.spec.ts
similarity index 58%
rename from packages/contracts-periphery/test/contracts/standards/L2StandardERC721.spec.ts
rename to packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721.spec.ts
index dcc23fbb8a1c48df307d29d0b2ff4fbe070453d6..45bbdc509f4abd469bebe8bb699ef4444ca01bf9 100644
--- a/packages/contracts-periphery/test/contracts/standards/L2StandardERC721.spec.ts
+++ b/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721.spec.ts
@@ -10,11 +10,11 @@ const TOKEN_ID = 10
 const DUMMY_L1ERC721_ADDRESS: string =
   '0x2234223412342234223422342234223422342234'
 
-describe('L2StandardERC721', () => {
+describe('OptimismMintableERC721', () => {
   let l2BridgeImpersonator: Signer
   let alice: Signer
   let Fake__L2ERC721Bridge: FakeContract
-  let L2StandardERC721: Contract
+  let OptimismMintableERC721: Contract
   let l2BridgeImpersonatorAddress: string
   let aliceAddress: string
   let baseUri: string
@@ -34,8 +34,8 @@ describe('L2StandardERC721', () => {
       '/tokenURI?uint256='
     )
 
-    L2StandardERC721 = await (
-      await ethers.getContractFactory('L2StandardERC721')
+    OptimismMintableERC721 = await (
+      await ethers.getContractFactory('OptimismMintableERC721')
     ).deploy(
       l2BridgeImpersonatorAddress,
       DUMMY_L1ERC721_ADDRESS,
@@ -52,7 +52,7 @@ describe('L2StandardERC721', () => {
     )
 
     // mint an nft to alice
-    await L2StandardERC721.connect(l2BridgeImpersonator).mint(
+    await OptimismMintableERC721.connect(l2BridgeImpersonator).mint(
       aliceAddress,
       TOKEN_ID,
       {
@@ -63,56 +63,62 @@ describe('L2StandardERC721', () => {
 
   describe('constructor', () => {
     it('should be able to create a standard ERC721 contract with the correct parameters', async () => {
-      expect(await L2StandardERC721.l2Bridge()).to.equal(
+      expect(await OptimismMintableERC721.bridge()).to.equal(
         l2BridgeImpersonatorAddress
       )
-      expect(await L2StandardERC721.l1Token()).to.equal(DUMMY_L1ERC721_ADDRESS)
-      expect(await L2StandardERC721.name()).to.equal('L2ERC721')
-      expect(await L2StandardERC721.symbol()).to.equal('ERC')
-      expect(await L2StandardERC721.baseTokenURI()).to.equal(baseUri)
+      expect(await OptimismMintableERC721.remoteToken()).to.equal(
+        DUMMY_L1ERC721_ADDRESS
+      )
+      expect(await OptimismMintableERC721.name()).to.equal('L2ERC721')
+      expect(await OptimismMintableERC721.symbol()).to.equal('ERC')
+      expect(await OptimismMintableERC721.baseTokenURI()).to.equal(baseUri)
 
       // alice has been minted an nft
-      expect(await L2StandardERC721.ownerOf(TOKEN_ID)).to.equal(aliceAddress)
+      expect(await OptimismMintableERC721.ownerOf(TOKEN_ID)).to.equal(
+        aliceAddress
+      )
     })
   })
 
   describe('mint and burn', () => {
     it('should not allow anyone but the L2 bridge to mint and burn', async () => {
       await expect(
-        L2StandardERC721.connect(alice).mint(aliceAddress, 100)
-      ).to.be.revertedWith('Only L2 Bridge can mint and burn')
+        OptimismMintableERC721.connect(alice).mint(aliceAddress, 100)
+      ).to.be.revertedWith(
+        'OptimismMintableERC721: only bridge can call this function'
+      )
       await expect(
-        L2StandardERC721.connect(alice).burn(aliceAddress, 100)
-      ).to.be.revertedWith('Only L2 Bridge can mint and burn')
+        OptimismMintableERC721.connect(alice).burn(aliceAddress, 100)
+      ).to.be.revertedWith(
+        'OptimismMintableERC721: only bridge can call this function'
+      )
     })
   })
 
   describe('supportsInterface', () => {
     it('should return the correct interface support', async () => {
-      const supportsERC165 = await L2StandardERC721.supportsInterface(
-        0x01ffc9a7
-      )
-      expect(supportsERC165).to.be.true
+      // ERC165
+      expect(await OptimismMintableERC721.supportsInterface(0x01ffc9a7)).to.be
+        .true
 
-      const supportsL2TokenInterface = await L2StandardERC721.supportsInterface(
-        0x1d1d8b63
-      )
-      expect(supportsL2TokenInterface).to.be.true
+      // OptimismMintablERC721
+      expect(await OptimismMintableERC721.supportsInterface(0xec4fc8e3)).to.be
+        .true
 
-      const supportsERC721Interface = await L2StandardERC721.supportsInterface(
-        0x80ac58cd
-      )
-      expect(supportsERC721Interface).to.be.true
+      // ERC721
+      expect(await OptimismMintableERC721.supportsInterface(0x80ac58cd)).to.be
+        .true
 
-      const badSupports = await L2StandardERC721.supportsInterface(0xffffffff)
-      expect(badSupports).to.be.false
+      // Some bad interface
+      expect(await OptimismMintableERC721.supportsInterface(0xffffffff)).to.be
+        .false
     })
   })
 
   describe('tokenURI', () => {
     it('should return the correct token uri', async () => {
       const tokenUri = baseUri.concat(TOKEN_ID.toString())
-      expect(await L2StandardERC721.tokenURI(TOKEN_ID)).to.equal(tokenUri)
+      expect(await OptimismMintableERC721.tokenURI(TOKEN_ID)).to.equal(tokenUri)
     })
   })
 })
diff --git a/packages/contracts-periphery/test/contracts/L2/messaging/L2StandardERC721Factory.spec.ts b/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721Factory.spec.ts
similarity index 50%
rename from packages/contracts-periphery/test/contracts/L2/messaging/L2StandardERC721Factory.spec.ts
rename to packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721Factory.spec.ts
index 17932675a53e8bb6655145b9f1b9366d0d5ba0e5..3a82f4b8b10e2be854e644fbcdb5e96cdabd7349 100644
--- a/packages/contracts-periphery/test/contracts/L2/messaging/L2StandardERC721Factory.spec.ts
+++ b/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721Factory.spec.ts
@@ -8,17 +8,17 @@ import {
 } from '@defi-wonderland/smock'
 
 /* Internal Imports */
-import { expect } from '../../../setup'
+import { expect } from '../../setup'
 
 const DUMMY_L2_BRIDGE_ADDRESS: string = ethers.utils.getAddress(
   '0x' + 'acdc'.repeat(10)
 )
 
-describe('L2StandardERC721Factory', () => {
+describe('OptimismMintableERC721Factory', () => {
   let signer: Signer
   let Factory__L1ERC721: MockContractFactory<ContractFactory>
   let L1ERC721: MockContract<Contract>
-  let L2StandardERC721Factory: Contract
+  let OptimismMintableERC721Factory: Contract
   let baseURI: string
   let chainId: number
 
@@ -31,8 +31,8 @@ describe('L2StandardERC721Factory', () => {
     )
     L1ERC721 = await Factory__L1ERC721.deploy('L1ERC721', 'ERC')
 
-    L2StandardERC721Factory = await (
-      await ethers.getContractFactory('L2StandardERC721Factory')
+    OptimismMintableERC721Factory = await (
+      await ethers.getContractFactory('OptimismMintableERC721Factory')
     ).deploy(DUMMY_L2_BRIDGE_ADDRESS)
 
     chainId = await signer.getChainId()
@@ -46,71 +46,60 @@ describe('L2StandardERC721Factory', () => {
   })
 
   it('should be deployed with the correct constructor argument', async () => {
-    expect(await L2StandardERC721Factory.l2ERC721Bridge()).to.equal(
+    expect(await OptimismMintableERC721Factory.bridge()).to.equal(
       DUMMY_L2_BRIDGE_ADDRESS
     )
   })
 
   it('should be able to create a standard ERC721 contract', async () => {
-    const tx = await L2StandardERC721Factory.createStandardL2ERC721(
-      L1ERC721.address,
-      'L2ERC721',
-      'ERC'
-    )
+    const tx =
+      await OptimismMintableERC721Factory.createStandardOptimismMintableERC721(
+        L1ERC721.address,
+        'L2ERC721',
+        'ERC'
+      )
     const receipt = await tx.wait()
 
-    // Get the StandardL2ERC721Created event
+    // Get the OptimismMintableERC721Created event
     const erc721CreatedEvent = receipt.events[0]
 
     // Expect there to be an event emitted for the standard token creation
-    expect(erc721CreatedEvent.event).to.be.eq('StandardL2ERC721Created')
+    expect(erc721CreatedEvent.event).to.be.eq('OptimismMintableERC721Created')
 
     // Get the L2 ERC721 address from the emitted event and check it was created correctly
-    const l2ERC721Address = erc721CreatedEvent.args._l2Token
-    const L2StandardERC721 = new Contract(
+    const l2ERC721Address = erc721CreatedEvent.args.localToken
+    const OptimismMintableERC721 = new Contract(
       l2ERC721Address,
-      (await ethers.getContractFactory('L2StandardERC721')).interface,
+      (await ethers.getContractFactory('OptimismMintableERC721')).interface,
       signer
     )
 
-    expect(await L2StandardERC721.l2Bridge()).to.equal(DUMMY_L2_BRIDGE_ADDRESS)
-    expect(await L2StandardERC721.l1Token()).to.equal(L1ERC721.address)
-    expect(await L2StandardERC721.name()).to.equal('L2ERC721')
-    expect(await L2StandardERC721.symbol()).to.equal('ERC')
-    expect(await L2StandardERC721.baseTokenURI()).to.equal(baseURI)
+    expect(await OptimismMintableERC721.bridge()).to.equal(
+      DUMMY_L2_BRIDGE_ADDRESS
+    )
+    expect(await OptimismMintableERC721.remoteToken()).to.equal(
+      L1ERC721.address
+    )
+    expect(await OptimismMintableERC721.name()).to.equal('L2ERC721')
+    expect(await OptimismMintableERC721.symbol()).to.equal('ERC')
+    expect(await OptimismMintableERC721.baseTokenURI()).to.equal(baseURI)
 
     expect(
-      await L2StandardERC721Factory.isStandardERC721(L2StandardERC721.address)
+      await OptimismMintableERC721Factory.isStandardOptimismMintableERC721(
+        OptimismMintableERC721.address
+      )
     ).to.equal(true)
-    expect(
-      await L2StandardERC721Factory.standardERC721Mapping(L1ERC721.address)
-    ).to.equal(l2ERC721Address)
   })
 
   it('should not be able to create a standard token with a 0 address for l1 token', async () => {
     await expect(
-      L2StandardERC721Factory.createStandardL2ERC721(
+      OptimismMintableERC721Factory.createStandardOptimismMintableERC721(
         ethers.constants.AddressZero,
         'L2ERC721',
         'ERC'
       )
-    ).to.be.revertedWith('Must provide L1 token address')
-  })
-
-  it('should not be able create two l2 standard tokens with the same l1 token', async () => {
-    // The first call will not revert
-    await L2StandardERC721Factory.createStandardL2ERC721(
-      L1ERC721.address,
-      'L2ERC721',
-      'ERC'
+    ).to.be.revertedWith(
+      'OptimismMintableERC721Factory: L1 token address cannot be address(0)'
     )
-
-    await expect(
-      L2StandardERC721Factory.createStandardL2ERC721(
-        L1ERC721.address,
-        'L2ERC721',
-        'ERC'
-      )
-    ).to.be.revertedWith('L2 Standard Token already exists for this L1 Token')
   })
 })
diff --git a/yarn.lock b/yarn.lock
index 0637d1b1e696aec3e87a66037875d151472a1cc5..3971985cac009194b9c0e42424ff77c5ac8c68ba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2812,16 +2812,16 @@
   resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.3.2.tgz#92df481362e366c388fc02133cf793029c744cea"
   integrity sha512-i/pOaOtcqDk4UqsrOv735uYyTbn6dvfiuVu5hstsgV6c4ZKUtu88/31zT2BzkCg+3JfcwOfgg2TtRKVKKZIGkQ==
 
+"@openzeppelin/contracts-upgradeable@4.6.0", "@openzeppelin/contracts-upgradeable@^4.5.2":
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.6.0.tgz#1bf55f230f008554d4c6fe25eb165b85112108b0"
+  integrity sha512-5OnVuO4HlkjSCJO165a4i2Pu1zQGzMs//o54LPrwUgxvEO2P3ax1QuaSI0cEHHTveA77guS0PnNugpR2JMsPfA==
+
 "@openzeppelin/contracts-upgradeable@^4.3.2":
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.4.0.tgz#85161d87c840c5bce2b6ed0c727b407e774852ae"
   integrity sha512-hIEyWJHu7bDTv6ckxOaV+K3+7mVzhjtyvp3QSaz56Rk5PscXtPAbkiNTb3yz6UJCWHPWpxVyULVgZ6RubuFEZg==
 
-"@openzeppelin/contracts-upgradeable@^4.5.2":
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.6.0.tgz#1bf55f230f008554d4c6fe25eb165b85112108b0"
-  integrity sha512-5OnVuO4HlkjSCJO165a4i2Pu1zQGzMs//o54LPrwUgxvEO2P3ax1QuaSI0cEHHTveA77guS0PnNugpR2JMsPfA==
-
 "@openzeppelin/contracts@3.4.1-solc-0.7-2":
   version "3.4.1-solc-0.7-2"
   resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92"