From adac074073e53fe82688aad6f7a91a714822db6f Mon Sep 17 00:00:00 2001 From: Vitor Marthendal Nunes Date: Fri, 5 Apr 2024 15:01:17 -0300 Subject: [PATCH] Feat: bridged nfts page (#8) * feat: add bridged collections to event cache * wip * feat: add bridged NFTs page * fix: build errors * feat: add account history to header * fix: header style --- apps/event-cache/abis/L1ERC721Bridge.ts | 644 +++++++++--------- apps/event-cache/abis/L2ERC721Bridge.ts | 2 +- .../abis/OptimismMintableERC721Factory.ts | 2 +- apps/event-cache/abis/index.ts | 3 + apps/event-cache/clients/index.ts | 21 + apps/event-cache/constants/index.ts | 3 +- apps/event-cache/ponder.config.ts | 74 +- apps/event-cache/ponder.schema.ts | 14 + apps/event-cache/src/L1ERC721Bridge.ts | 101 +++ apps/event-cache/src/L2ERC721Bridge.ts | 89 +++ .../src/OptimismMintableERC721Factory.ts | 25 +- .../account-history/[chainId]/page.tsx | 196 ++++++ .../app/(general)/account-history/page.tsx | 20 + .../blockchain/block-explorer-link.tsx | 35 +- .../erc721/erc721-collection-selector.tsx | 8 +- .../blockchain/transaction-status.tsx | 2 +- .../components/forms/form-L1-to-L2-bridge.tsx | 1 - .../forms/form-create-l2-erc721.tsx | 4 +- apps/website/components/layout/footer.tsx | 2 +- .../website/components/layout/site-header.tsx | 15 +- .../components/shared/app-mode-selector.tsx | 4 +- .../website/components/ui/navigation-menu.tsx | 12 +- apps/website/lib/event-cache/gql/gql.ts | 8 + apps/website/lib/event-cache/gql/graphql.ts | 327 +++++++++ .../event-cache/hooks/use-bridged-erc721.tsx | 50 ++ ...timism-mintable-erc721-by-local-token.tsx} | 2 +- ...imism-mintable-erc721-by-remote-token.tsx} | 2 +- 27 files changed, 1278 insertions(+), 388 deletions(-) create mode 100644 apps/event-cache/abis/index.ts create mode 100644 apps/event-cache/clients/index.ts create mode 100644 apps/event-cache/src/L1ERC721Bridge.ts create mode 100644 apps/event-cache/src/L2ERC721Bridge.ts create mode 100644 apps/website/app/(general)/account-history/[chainId]/page.tsx create mode 100644 apps/website/app/(general)/account-history/page.tsx create mode 100644 apps/website/lib/event-cache/hooks/use-bridged-erc721.tsx rename apps/website/lib/event-cache/hooks/{use-get-optimism-mintable-erc721-by-local-token.tsx => use-optimism-mintable-erc721-by-local-token.tsx} (92%) rename apps/website/lib/event-cache/hooks/{use-get-optimism-mintable-erc721-by-remote-token.tsx => use-optimism-mintable-erc721-by-remote-token.tsx} (92%) diff --git a/apps/event-cache/abis/L1ERC721Bridge.ts b/apps/event-cache/abis/L1ERC721Bridge.ts index 852f4ce..b78c56b 100644 --- a/apps/event-cache/abis/L1ERC721Bridge.ts +++ b/apps/event-cache/abis/L1ERC721Bridge.ts @@ -1,322 +1,322 @@ -export const L1ERC721Bridge = [ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "localToken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "remoteToken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "extraData", - "type": "bytes" - } - ], - "name": "ERC721BridgeFinalized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "localToken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "remoteToken", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "extraData", - "type": "bytes" - } - ], - "name": "ERC721BridgeInitiated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "inputs": [], - "name": "MESSENGER", - "outputs": [ - { - "internalType": "contract CrossDomainMessenger", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "OTHER_BRIDGE", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_localToken", - "type": "address" - }, - { - "internalType": "address", - "name": "_remoteToken", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "_minGasLimit", - "type": "uint32" - }, - { - "internalType": "bytes", - "name": "_extraData", - "type": "bytes" - } - ], - "name": "bridgeERC721", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_localToken", - "type": "address" - }, - { - "internalType": "address", - "name": "_remoteToken", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "_minGasLimit", - "type": "uint32" - }, - { - "internalType": "bytes", - "name": "_extraData", - "type": "bytes" - } - ], - "name": "bridgeERC721To", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "deposits", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_localToken", - "type": "address" - }, - { - "internalType": "address", - "name": "_remoteToken", - "type": "address" - }, - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_extraData", - "type": "bytes" - } - ], - "name": "finalizeBridgeERC721", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract CrossDomainMessenger", - "name": "_messenger", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "messenger", - "outputs": [ - { - "internalType": "contract CrossDomainMessenger", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "otherBridge", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } - ] as const; \ No newline at end of file +export const L1ERC721BridgeAbi = [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC721BridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC721BridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "inputs": [], + "name": "MESSENGER", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OTHER_BRIDGE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "bridgeERC721To", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "deposits", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_extraData", + "type": "bytes" + } + ], + "name": "finalizeBridgeERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "_messenger", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "otherBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] as const; \ No newline at end of file diff --git a/apps/event-cache/abis/L2ERC721Bridge.ts b/apps/event-cache/abis/L2ERC721Bridge.ts index 6db5936..9040776 100644 --- a/apps/event-cache/abis/L2ERC721Bridge.ts +++ b/apps/event-cache/abis/L2ERC721Bridge.ts @@ -1 +1 @@ -export const L1ERC721Bridge = [{"inputs":[{"internalType":"address","name":"_messenger","type":"address"},{"internalType":"address","name":"_otherBridge","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"localToken","type":"address"},{"indexed":true,"internalType":"address","name":"remoteToken","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"ERC721BridgeFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"localToken","type":"address"},{"indexed":true,"internalType":"address","name":"remoteToken","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"ERC721BridgeInitiated","type":"event"},{"inputs":[],"name":"MESSENGER","outputs":[{"internalType":"contract CrossDomainMessenger","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OTHER_BRIDGE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_localToken","type":"address"},{"internalType":"address","name":"_remoteToken","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"bridgeERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_localToken","type":"address"},{"internalType":"address","name":"_remoteToken","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"bridgeERC721To","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_localToken","type":"address"},{"internalType":"address","name":"_remoteToken","type":"address"},{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"finalizeBridgeERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messenger","outputs":[{"internalType":"contract CrossDomainMessenger","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"otherBridge","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}] as const; \ No newline at end of file +export const L2ERC721BridgeAbi = [{ "inputs": [{ "internalType": "address", "name": "_messenger", "type": "address" }, { "internalType": "address", "name": "_otherBridge", "type": "address" }], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "localToken", "type": "address" }, { "indexed": true, "internalType": "address", "name": "remoteToken", "type": "address" }, { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": false, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "indexed": false, "internalType": "bytes", "name": "extraData", "type": "bytes" }], "name": "ERC721BridgeFinalized", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "localToken", "type": "address" }, { "indexed": true, "internalType": "address", "name": "remoteToken", "type": "address" }, { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": false, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "indexed": false, "internalType": "bytes", "name": "extraData", "type": "bytes" }], "name": "ERC721BridgeInitiated", "type": "event" }, { "inputs": [], "name": "MESSENGER", "outputs": [{ "internalType": "contract CrossDomainMessenger", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "OTHER_BRIDGE", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_localToken", "type": "address" }, { "internalType": "address", "name": "_remoteToken", "type": "address" }, { "internalType": "uint256", "name": "_tokenId", "type": "uint256" }, { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, { "internalType": "bytes", "name": "_extraData", "type": "bytes" }], "name": "bridgeERC721", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_localToken", "type": "address" }, { "internalType": "address", "name": "_remoteToken", "type": "address" }, { "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_tokenId", "type": "uint256" }, { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, { "internalType": "bytes", "name": "_extraData", "type": "bytes" }], "name": "bridgeERC721To", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_localToken", "type": "address" }, { "internalType": "address", "name": "_remoteToken", "type": "address" }, { "internalType": "address", "name": "_from", "type": "address" }, { "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_tokenId", "type": "uint256" }, { "internalType": "bytes", "name": "_extraData", "type": "bytes" }], "name": "finalizeBridgeERC721", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "messenger", "outputs": [{ "internalType": "contract CrossDomainMessenger", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "otherBridge", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }] as const; \ No newline at end of file diff --git a/apps/event-cache/abis/OptimismMintableERC721Factory.ts b/apps/event-cache/abis/OptimismMintableERC721Factory.ts index e6cdb47..9d97927 100644 --- a/apps/event-cache/abis/OptimismMintableERC721Factory.ts +++ b/apps/event-cache/abis/OptimismMintableERC721Factory.ts @@ -1,4 +1,4 @@ -export const OptimismMintableERC721Factory = [ +export const OptimismMintableERC721FactoryAbi = [ { inputs: [ { diff --git a/apps/event-cache/abis/index.ts b/apps/event-cache/abis/index.ts new file mode 100644 index 0000000..796e944 --- /dev/null +++ b/apps/event-cache/abis/index.ts @@ -0,0 +1,3 @@ +export * from "./L1ERC721Bridge" +export * from "./L2ERC721Bridge" +export * from "./OptimismMintableERC721Factory" diff --git a/apps/event-cache/clients/index.ts b/apps/event-cache/clients/index.ts new file mode 100644 index 0000000..6afd3f6 --- /dev/null +++ b/apps/event-cache/clients/index.ts @@ -0,0 +1,21 @@ +import { createPublicClient, http } from 'viem'; +import { mainnet, sepolia } from "viem/chains"; + + +const mainnetPublicClient = createPublicClient({ + chain: mainnet, + transport: http(process.env.PONDER_RPC_URL_1) +}) + +const sepoliaPublicClient = createPublicClient({ + chain: sepolia, + transport: http(process.env.PONDER_RPC_URL_11155111) +}) + +export function getL1PublicClient(l2chainId: number) { + if (l2chainId === 11155420 || l2chainId === 84532) { + return sepoliaPublicClient; + } else { + return mainnetPublicClient; + } +} \ No newline at end of file diff --git a/apps/event-cache/constants/index.ts b/apps/event-cache/constants/index.ts index 29e427f..7368c06 100644 --- a/apps/event-cache/constants/index.ts +++ b/apps/event-cache/constants/index.ts @@ -1 +1,2 @@ -export const OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS = "0x4200000000000000000000000000000000000017" \ No newline at end of file +export const OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS = "0x4200000000000000000000000000000000000017" +export const OPTIMISM_L2_ERC721_BRIDGE_ADDRESS = "0x4200000000000000000000000000000000000014" \ No newline at end of file diff --git a/apps/event-cache/ponder.config.ts b/apps/event-cache/ponder.config.ts index a0a3ada..56850a4 100644 --- a/apps/event-cache/ponder.config.ts +++ b/apps/event-cache/ponder.config.ts @@ -1,10 +1,18 @@ import { createConfig } from "@ponder/core"; import { http } from "viem"; -import { OptimismMintableERC721Factory } from "./abis/OptimismMintableERC721Factory"; -import { OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS } from "./constants"; +import { L1ERC721BridgeAbi, L2ERC721BridgeAbi, OptimismMintableERC721FactoryAbi } from "./abis"; +import { OPTIMISM_L2_ERC721_BRIDGE_ADDRESS, OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS } from "./constants"; export default createConfig({ networks: { + ethereum: { + chainId: 1, + transport: http(process.env.PONDER_RPC_URL_1), + }, + sepolia: { + chainId: 11155111, + transport: http(process.env.PONDER_RPC_URL_11155111), + }, optimism: { chainId: 10, transport: http(process.env.PONDER_RPC_URL_10), @@ -25,31 +33,81 @@ export default createConfig({ contracts: { OptimismMintableERC721Factory: { + abi: OptimismMintableERC721FactoryAbi, + filter: { event: "OptimismMintableERC721Created" }, + address: OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS, network: { // Production optimism: { - address: OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS, // Block number of the first transaction startBlock: 116002023 }, base: { - address: OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS, // Block number of the first transaction startBlock: 2297164 }, - // // Testnet + // Testnet optimismSepolia: { - address: OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS, // Block number of the first transaction startBlock: 7074623 }, baseSepolia: { - address: OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS, // Block number of the first transaction startBlock: 6932093 }, }, - abi: OptimismMintableERC721Factory, }, + L1ERC721Bridge: { + abi: L1ERC721BridgeAbi, + filter: { event: ["ERC721BridgeInitiated", "ERC721BridgeFinalized"] }, + network: { + // Production + ethereum: { + address: [ + // Optimism + "0x5a7749f83b81B301cAb5f48EB8516B986DAef23D", + // Base + "0x608d94945A64503E642E6370Ec598e519a2C1E53", + ], + // Block number of the first transaction + startBlock: 15677422, + }, + sepolia: { + address: [ + // Optimism Sepolia Testnet + "0xd83e03D576d23C9AEab8cC44Fa98d058D2176D1f", + // Base Sepolia Testnet + "0x21eFD066e581FA55Ef105170Cc04d74386a09190" + ], + // Block number of the first transaction + startBlock: 5126689 + }, + }, + }, + L2ERC721Bridge: { + abi: L2ERC721BridgeAbi, + filter: { event: ["ERC721BridgeInitiated", "ERC721BridgeFinalized"] }, + address: OPTIMISM_L2_ERC721_BRIDGE_ADDRESS, + network: { + // Production + optimism: { + // Block number of the first transaction + startBlock: 0 + }, + base: { + // Block number of the first transaction + startBlock: 0 + }, + // Testnet + optimismSepolia: { + // Block number of the first transaction + startBlock: 0 + }, + baseSepolia: { + // Block number of the first transaction + startBlock: 0 + }, + }, + } }, }); diff --git a/apps/event-cache/ponder.schema.ts b/apps/event-cache/ponder.schema.ts index 7644def..bc05f47 100644 --- a/apps/event-cache/ponder.schema.ts +++ b/apps/event-cache/ponder.schema.ts @@ -1,6 +1,7 @@ import { createSchema } from "@ponder/core"; export default createSchema((p) => ({ + BridgedErc721State: p.createEnum(["L1", "L2", "PENDING_TO_L1", "PENDING_TO_L2"]), OptimismMintableERC721: p.createTable({ id: p.string(), chainId: p.int(), @@ -13,4 +14,17 @@ export default createSchema((p) => ({ remoteSymbol: p.string().optional(), deployer: p.string(), }), + BridgedErc721: p.createTable({ + id: p.string(), + state: p.enum("BridgedErc721State"), + l1ChainId: p.int(), + l2ChainId: p.int(), + l1Token: p.string(), + l2Token: p.string(), + tokenId: p.string(), + timestamp: p.bigint(), + owner: p.string(), + txHash: p.string(), + txChainId: p.int(), + }), })); diff --git a/apps/event-cache/src/L1ERC721Bridge.ts b/apps/event-cache/src/L1ERC721Bridge.ts new file mode 100644 index 0000000..1b8287c --- /dev/null +++ b/apps/event-cache/src/L1ERC721Bridge.ts @@ -0,0 +1,101 @@ +import { ponder } from "@/generated"; +import { base, baseSepolia, optimism, optimismSepolia } from "viem/chains"; + +function getL2ChainId(l1ContractAddress: string | null) { + if (!l1ContractAddress) { + throw new Error("Invalid l1 contract Address") + } + const l1ContractToChainId: Record = { + "0x5a7749f83b81B301cAb5f48EB8516B986DAef23D": optimism.id, + "0x608d94945A64503E642E6370Ec598e519a2C1E53": base.id, + "0xd83e03D576d23C9AEab8cC44Fa98d058D2176D1f": optimismSepolia.id, + "0x21eFD066e581FA55Ef105170Cc04d74386a09190": baseSepolia.id + + } + const l2ChainId = l1ContractToChainId?.[l1ContractAddress] + if (!l2ChainId) { + throw new Error("Invalid l1 contract Address: " + l1ContractAddress) + } + return l2ChainId +} + + +ponder.on("L1ERC721Bridge:ERC721BridgeInitiated", async ({ event, context }) => { + const { BridgedErc721 } = context.db; + const timestamp = event.block.timestamp + const chainId = context.network.chainId + const { localToken: l1Token, remoteToken: l2Token, tokenId, to } = event.args + const l1ContractAddress = event.log.address + const l1ChainId = chainId + const l2ChainId = getL2ChainId(l1ContractAddress) + const id = `${l1ChainId}:${l1Token}:${tokenId}` + + const existingBridgedToken = await BridgedErc721.findUnique({ + id, + }); + + // Only update the token if it is the most recent event + if (!existingBridgedToken || timestamp > existingBridgedToken.timestamp) { + await BridgedErc721.upsert({ + id, + create: { + state: "PENDING_TO_L2", + l1ChainId, + owner: to, + l1Token, + timestamp, + l2Token, + tokenId: tokenId.toString(), + l2ChainId, + txHash: event.log.transactionHash, + txChainId: chainId + }, + update: { + state: "PENDING_TO_L2", + txHash: event.log.transactionHash, + txChainId: chainId + } + }); + } + + +}); + +ponder.on("L1ERC721Bridge:ERC721BridgeFinalized", async ({ event, context }) => { + const { BridgedErc721 } = context.db; + const timestamp = event.block.timestamp + const chainId = context.network.chainId + const l1ContractAddress = event.log.address + const { localToken: l1Token, remoteToken: l2Token, tokenId, to } = event.args + const l1ChainId = chainId + const l2ChainId = getL2ChainId(l1ContractAddress) + const id = `${l1ChainId}:${l1Token}:${tokenId}` + + const existingBridgedToken = await BridgedErc721.findUnique({ + id, + }); + + // Only update the token if it is the most recent event + if (!existingBridgedToken || timestamp > existingBridgedToken.timestamp) { + await BridgedErc721.upsert({ + id, + create: { + state: "L1", + l1ChainId, + owner: to, + l1Token, + timestamp, + l2Token, + tokenId: tokenId.toString(), + l2ChainId, + txHash: event.log.transactionHash, + txChainId: chainId + }, + update: { + state: "L1", + txHash: event.log.transactionHash, + txChainId: chainId + } + }); + } +}); \ No newline at end of file diff --git a/apps/event-cache/src/L2ERC721Bridge.ts b/apps/event-cache/src/L2ERC721Bridge.ts new file mode 100644 index 0000000..e22c45e --- /dev/null +++ b/apps/event-cache/src/L2ERC721Bridge.ts @@ -0,0 +1,89 @@ +import { ponder } from "@/generated"; +import { base, baseSepolia, mainnet, optimism, optimismSepolia, sepolia } from "viem/chains"; + +function getL1ChainId(l2ChainId: number) { + const productionChains: number[] = [base.id, optimism.id] + const testnetChains: number[] = [baseSepolia.id, optimismSepolia.id] + + if (productionChains.includes(l2ChainId)) { + return mainnet.id + } + if (testnetChains.includes(l2ChainId)) { + return sepolia.id + } + throw new Error("Invalid l2 chain id abc: " + l2ChainId) +} + +ponder.on("L2ERC721Bridge:ERC721BridgeInitiated", async ({ event, context }) => { + const { BridgedErc721 } = context.db; + const chainId = context.network.chainId + const timestamp = event.block.timestamp + const { remoteToken: l1Token, localToken: l2Token, tokenId } = event.args + const l1ChainId = getL1ChainId(chainId) + const l2ChainId = chainId + const id = `${l1ChainId}:${l1Token}:${tokenId}` + + const existingToken = await BridgedErc721.findUnique({ + id, + }); + + if (!existingToken || timestamp > existingToken.timestamp) { + await BridgedErc721.upsert({ + id, + update: { + state: "PENDING_TO_L1", + txHash: event.log.transactionHash, + txChainId: chainId + }, + create: { + state: "PENDING_TO_L1", + l1ChainId, + l2ChainId, + l2Token, + owner: event.args.to, + l1Token, + timestamp, + tokenId: tokenId.toString(), + txHash: event.log.transactionHash, + txChainId: chainId + } + }); + } +}); + +ponder.on("L2ERC721Bridge:ERC721BridgeFinalized", async ({ event, context }) => { + const { BridgedErc721 } = context.db; + const chainId = context.network.chainId + const timestamp = event.block.timestamp + const { remoteToken: l1Token, localToken: l2Token, tokenId } = event.args + const l1ChainId = getL1ChainId(chainId) + const l2ChainId = chainId + const id = `${l1ChainId}:${l1Token}:${tokenId}` + + const existingToken = await BridgedErc721.findUnique({ + id, + }); + + if (!existingToken || timestamp > existingToken.timestamp) { + await BridgedErc721.upsert({ + id, + update: { + state: "L2", + txHash: event.log.transactionHash, + txChainId: chainId + }, + create: { + state: "L2", + l1ChainId, + l2ChainId, + l2Token, + owner: event.args.to, + l1Token, + timestamp, + tokenId: tokenId.toString(), + txHash: event.log.transactionHash, + txChainId: chainId + } + }); + } +}); \ No newline at end of file diff --git a/apps/event-cache/src/OptimismMintableERC721Factory.ts b/apps/event-cache/src/OptimismMintableERC721Factory.ts index 9ab473d..7eddfc5 100644 --- a/apps/event-cache/src/OptimismMintableERC721Factory.ts +++ b/apps/event-cache/src/OptimismMintableERC721Factory.ts @@ -1,25 +1,6 @@ import { ponder } from "@/generated"; -import { createPublicClient, erc721Abi, http } from 'viem'; -import { mainnet, sepolia } from "viem/chains"; - - -const mainnetPublicClient = createPublicClient({ - chain: mainnet, - transport: http(process.env.PONDER_RPC_URL_1) -}) - -const sepoliaPublicClient = createPublicClient({ - chain: sepolia, - transport: http(process.env.PONDER_RPC_URL_11155111) -}) - -function getL1PublicClient(l2chainId: number) { - if (l2chainId === 11155420 || l2chainId === 84532) { - return sepoliaPublicClient; - } else { - return mainnetPublicClient; - } -} +import { erc721Abi } from "viem"; +import { getL1PublicClient } from "../clients"; ponder.on("OptimismMintableERC721Factory:OptimismMintableERC721Created", async ({ event, context }) => { const { OptimismMintableERC721 } = context.db; @@ -90,7 +71,7 @@ ponder.on("OptimismMintableERC721Factory:OptimismMintableERC721Created", async ( const { localName, localSymbol, remoteName, remoteSymbol } = tokenMetadata await OptimismMintableERC721.create({ - id: `${context.network.chainId}:${event.args.localToken}`, + id: `${context.network.chainId}:${event.args.localToken}:${event.args.remoteToken}`, data: { chainId: context.network.chainId, blockNumber: event.block.number, diff --git a/apps/website/app/(general)/account-history/[chainId]/page.tsx b/apps/website/app/(general)/account-history/[chainId]/page.tsx new file mode 100644 index 0000000..168cb46 --- /dev/null +++ b/apps/website/app/(general)/account-history/[chainId]/page.tsx @@ -0,0 +1,196 @@ +"use client" + +import { useEffect } from "react" +import Image from "next/image" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { l1NetworkOptions, l2NetworksOptions } from "@/data/networks/options" +import { LuExternalLink } from "react-icons/lu" +import { type Address, type Hex } from "viem" +import { useAccount } from "wagmi" + +import { + BridgedErc721State, + useBridgedERC721ByOwner, +} from "@/lib/event-cache/hooks/use-bridged-erc721" +import { useTokenList } from "@/lib/hooks/use-token-list" +import { useAppMode } from "@/lib/state/app-mode" +import { Card } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { BlockExplorerLink } from "@/components/blockchain/block-explorer-link" +import { ConnectButton } from "@/components/blockchain/connect-button" + +export default function BridgedCollectionsByChainIdPage({ + params: { chainId }, +}: { + params: { + chainId: string + } +}) { + const router = useRouter() + const { appMode } = useAppMode() + const { address } = useAccount() + const tokenList = useTokenList() + + const bridgedERC721ByOwnerQuery = useBridgedERC721ByOwner({ + params: { + owner: address, + l2chainId: Number(chainId), + states: [ + BridgedErc721State.L2, + BridgedErc721State.PendingToL1, + BridgedErc721State.PendingToL2, + ], + }, + query: { + enabled: !!address, + }, + }) + const l1Chain = l1NetworkOptions[appMode] || 1 + const l2Chain = l2NetworksOptions[appMode][Number(chainId)] || 10 + + useEffect(() => { + const newChainId = Object.values(l2NetworksOptions[appMode])[0].chainId + + // If AppMode changes, redirect to the first chain in the new mode + if (!l2NetworksOptions[appMode][Number(chainId)]) { + router.push(`/account-history/${newChainId}`) + } + }, [appMode]) + + return ( +
+

+ Your Bridged NFTs +

+ + + {Object.values(l2NetworksOptions[appMode]).map( + ({ name, chainId: optionChainId }) => ( + + + {name} + + + ) + )} + + +
+ {!address ? ( +
+ +
+ ) : bridgedERC721ByOwnerQuery.isLoading ? ( + <> + {Array.from({ length: 4 }).map((_, idx) => ( + + ))} + + ) : !bridgedERC721ByOwnerQuery.data || + bridgedERC721ByOwnerQuery.data?.bridgedErc721s.items.length === 0 ? ( +
+ No bridged NFTs found +
+ ) : ( + bridgedERC721ByOwnerQuery.data?.bridgedErc721s.items + .sort( + (a, b) => + Object.keys(BridgedErc721State).indexOf(a.state) - + Object.keys(BridgedErc721State).indexOf(b.state) + ) + .map((bridgedErc721) => { + const tokenMetadata = tokenList?.tokens.find( + (token) => token.address === bridgedErc721.l1Token + ) + return ( + +
+ {`logo`} +
+
+
+

+ {tokenMetadata?.name || "Unknown"} +

+

+ # {bridgedErc721.tokenId} +

+

+ Status:{" "} + {bridgedErc721.state === BridgedErc721State.L2 + ? l2NetworksOptions[appMode][bridgedErc721.l2ChainId] + ?.name || "L1" + : bridgedErc721.state === + BridgedErc721State.PendingToL1 + ? `Pending bridge to ${ + l1NetworkOptions[appMode]?.name || "L2" + }` + : `Pending bridge to ${ + l2NetworksOptions[appMode][ + bridgedErc721.l2ChainId + ]?.name || "L2" + }`} +

+
+
+
+ +
+
Transaction Details
+
+ {" "} + +
+
+
+
+
+
{l1Chain.name}
+ +
+
+
{l2Chain?.name}
+ +
+
+
+
+ ) + }) + )} +
+
+ ) +} diff --git a/apps/website/app/(general)/account-history/page.tsx b/apps/website/app/(general)/account-history/page.tsx new file mode 100644 index 0000000..e439629 --- /dev/null +++ b/apps/website/app/(general)/account-history/page.tsx @@ -0,0 +1,20 @@ +"use client" + +import { useEffect } from "react" +import { useRouter } from "next/navigation" +import { l2NetworksOptions } from "@/data/networks/options" + +import { useAppMode } from "@/lib/state/app-mode" + +export default function BridgedCollectionsPage() { + const { appMode } = useAppMode() + const router = useRouter() + + useEffect(() => { + router.push( + `/account-history/${Object.values(l2NetworksOptions[appMode])[0].chainId}` + ) + }, []) + + return null +} diff --git a/apps/website/components/blockchain/block-explorer-link.tsx b/apps/website/components/blockchain/block-explorer-link.tsx index 5b5b393..5355034 100644 --- a/apps/website/components/blockchain/block-explorer-link.tsx +++ b/apps/website/components/blockchain/block-explorer-link.tsx @@ -1,24 +1,33 @@ import { HTMLAttributes } from "react" -import { type Address } from "viem" +import { Hex, type Address } from "viem" import { useAccount } from "wagmi" import { config } from "@/config/wagmi" import { cn } from "@/lib/utils" -interface BlockExplorerLinkProps extends HTMLAttributes { - address: Address | undefined - showExplorerName?: boolean - chainId?: number - type?: "address" | "tx" -} +type BlockExplorerLinkProps = HTMLAttributes & + ( + | { + address: Address | undefined + tx?: never + showExplorerName?: boolean + chainId?: number + } + | { + address?: never + tx: Hex | undefined + showExplorerName?: boolean + chainId?: number + } + ) export const BlockExplorerLink = ({ address, + tx, children, className, chainId, showExplorerName, - type = "address", ...props }: BlockExplorerLinkProps) => { const { chain } = useAccount() @@ -28,7 +37,9 @@ export const BlockExplorerLink = ({ : chain?.blockExplorers?.default chain?.blockExplorers?.default - if (!address) return null + if (!address && !tx) return null + + const type = tx ? "tx" : "address" return ( {blockExplorer && ( - {showExplorerName ? blockExplorer.name : children ?? address} + {showExplorerName + ? blockExplorer.name + : children ?? address ?? tx ?? ""} )} diff --git a/apps/website/components/blockchain/erc721/erc721-collection-selector.tsx b/apps/website/components/blockchain/erc721/erc721-collection-selector.tsx index 3a48c4e..02ff8a9 100644 --- a/apps/website/components/blockchain/erc721/erc721-collection-selector.tsx +++ b/apps/website/components/blockchain/erc721/erc721-collection-selector.tsx @@ -5,8 +5,8 @@ import Image from "next/image" import { l2NetworksOptions } from "@/data/networks/options" import { Address, checksumAddress, isAddress } from "viem" -import { useGetOtimismMintableERC721ByLocalTokenQuery } from "@/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-local-token" -import { useGetOtimismMintableERC721ByRemoteTokenQuery } from "@/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-remote-token" +import { useOtimismMintableERC721ByLocalTokenQuery } from "@/lib/event-cache/hooks/use-optimism-mintable-erc721-by-local-token" +import { useOtimismMintableERC721ByRemoteTokenQuery } from "@/lib/event-cache/hooks/use-optimism-mintable-erc721-by-remote-token" import { type Nft } from "@/lib/hooks/web3/use-nfts-for-owner" import { AppMode } from "@/lib/state/app-mode" import { cn } from "@/lib/utils" @@ -62,7 +62,7 @@ export function Erc721CollectionSelector({ const [searchValue, setSearchValue] = useState("") const getOtimismMintableERC721ByRemoteTokenQuery = - useGetOtimismMintableERC721ByRemoteTokenQuery({ + useOtimismMintableERC721ByRemoteTokenQuery({ remoteToken: isAddress(selectedUnlistedToken ?? searchValue) ? checksumAddress(selectedUnlistedToken ?? (searchValue as Address)) : "0x0", @@ -74,7 +74,7 @@ export function Erc721CollectionSelector({ }) const getOtimismMintableERC721ByLocalTokenQuery = - useGetOtimismMintableERC721ByLocalTokenQuery({ + useOtimismMintableERC721ByLocalTokenQuery({ chainId, localToken: isAddress(selectedUnlistedToken ?? searchValue) ? checksumAddress(selectedUnlistedToken ?? (searchValue as Address)) diff --git a/apps/website/components/blockchain/transaction-status.tsx b/apps/website/components/blockchain/transaction-status.tsx index 734a5bf..f6ac7e0 100644 --- a/apps/website/components/blockchain/transaction-status.tsx +++ b/apps/website/components/blockchain/transaction-status.tsx @@ -30,7 +30,7 @@ export const TransactionStatus = ({ {(isLoadingTx || isSuccess) && ( <> {isLoadingTx ? "Processing..." : "Success!"} - + )} diff --git a/apps/website/components/forms/form-L1-to-L2-bridge.tsx b/apps/website/components/forms/form-L1-to-L2-bridge.tsx index c01c155..eec87a0 100644 --- a/apps/website/components/forms/form-L1-to-L2-bridge.tsx +++ b/apps/website/components/forms/form-L1-to-L2-bridge.tsx @@ -58,7 +58,6 @@ export function FormL1ToL2Bridge({ ...props }: FormL1ToL2BridgeProps) { const [imageLoaded, setImageLoaded] = useState(false) - const l1Chain = l1NetworkOptions[appMode] const l2Chains = l2NetworksOptions[appMode] diff --git a/apps/website/components/forms/form-create-l2-erc721.tsx b/apps/website/components/forms/form-create-l2-erc721.tsx index bafae09..8b22a62 100644 --- a/apps/website/components/forms/form-create-l2-erc721.tsx +++ b/apps/website/components/forms/form-create-l2-erc721.tsx @@ -21,7 +21,7 @@ import { } from "wagmi" import { z } from "zod" -import { useGetOtimismMintableERC721ByRemoteTokenQuery } from "@/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-remote-token" +import { useOtimismMintableERC721ByRemoteTokenQuery } from "@/lib/event-cache/hooks/use-optimism-mintable-erc721-by-remote-token" import { useReadErc721Name, useReadErc721Symbol, @@ -110,7 +110,7 @@ export const FormCreateL2ERC721 = ({ ] const getOtimismMintableERC721ByRemoteTokenQuery = - useGetOtimismMintableERC721ByRemoteTokenQuery({ + useOtimismMintableERC721ByRemoteTokenQuery({ chainId: Number(watchL2ChainId), remoteToken: isAddress(watchRemoteToken) ? checksumAddress(watchRemoteToken) diff --git a/apps/website/components/layout/footer.tsx b/apps/website/components/layout/footer.tsx index 479c706..f453ce0 100644 --- a/apps/website/components/layout/footer.tsx +++ b/apps/website/components/layout/footer.tsx @@ -74,7 +74,7 @@ export function Footer({ className, ...props }: HTMLAttributes) {
{menuItems.map(({ href, title }) => ( - + {title} ))} diff --git a/apps/website/components/layout/site-header.tsx b/apps/website/components/layout/site-header.tsx index dce0967..15f0f78 100644 --- a/apps/website/components/layout/site-header.tsx +++ b/apps/website/components/layout/site-header.tsx @@ -6,12 +6,13 @@ import { ConnectButton } from "@/components/blockchain/connect-button" import { AppModeSelector } from "../shared/app-mode-selector" import { LinkComponent } from "../shared/link-component" +import { buttonVariants } from "../ui/button" export function SiteHeader() { return (
@@ -28,8 +29,16 @@ export function SiteHeader() {
-
- +
+ + Account History + +
+ +
) diff --git a/apps/website/components/shared/app-mode-selector.tsx b/apps/website/components/shared/app-mode-selector.tsx index 66848db..ddc73d6 100644 --- a/apps/website/components/shared/app-mode-selector.tsx +++ b/apps/website/components/shared/app-mode-selector.tsx @@ -20,12 +20,12 @@ export function AppModeSelector() { {appMode === "mainnet" ? ( <> ๐Ÿš€ - Production + Production ) : ( <> ๐Ÿงช - Testing + Testing )}
diff --git a/apps/website/components/ui/navigation-menu.tsx b/apps/website/components/ui/navigation-menu.tsx index 2f6f52e..7a3d9ac 100644 --- a/apps/website/components/ui/navigation-menu.tsx +++ b/apps/website/components/ui/navigation-menu.tsx @@ -41,7 +41,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName const NavigationMenuItem = NavigationMenuPrimitive.Item const navigationMenuTriggerStyle = cva( - "group inline-flex h-9 w-max items-center justify-center rounded-md bg-transparent px-4 py-2 text-base font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-background data-[state=open]:bg-backgroud" + "group inline-flex h-9 w-max items-center justify-center rounded-md bg-transparent px-4 py-2 text-base font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-primary data-[state=open]:bg-primary data-[active]:text-secondary data-[state=open]:text-secondary" ) const NavigationMenuTrigger = React.forwardRef< @@ -116,13 +116,13 @@ NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName export { - navigationMenuTriggerStyle, NavigationMenu, - NavigationMenuList, - NavigationMenuItem, NavigationMenuContent, - NavigationMenuTrigger, - NavigationMenuLink, NavigationMenuIndicator, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, NavigationMenuViewport, + navigationMenuTriggerStyle, } diff --git a/apps/website/lib/event-cache/gql/gql.ts b/apps/website/lib/event-cache/gql/gql.ts index e43e18e..49fa8b8 100644 --- a/apps/website/lib/event-cache/gql/gql.ts +++ b/apps/website/lib/event-cache/gql/gql.ts @@ -16,6 +16,8 @@ import * as types from "./graphql" const documents = { "\n query allOptimismMintableERC721Query($limit: Int) {\n optimismMintableERC721s(limit: $limit) {\n items {\n id\n chainId\n blockNumber\n localToken\n localName\n localSymbol\n remoteToken\n remoteName\n remoteSymbol\n deployer\n }\n }\n }\n": types.AllOptimismMintableErc721QueryDocument, + '\n query getBridgedERC721Query(\n $owner: String\n $l2chainId: Int\n $states: [BridgedErc721State]\n ) {\n bridgedErc721s(\n where: { owner: $owner, l2ChainId: $l2chainId, state_in: $states }\n orderBy: "timestamp"\n orderDirection: "desc"\n ) {\n items {\n id\n state\n l1ChainId\n l2ChainId\n l1Token\n l2Token\n tokenId\n owner\n txHash\n txChainId\n timestamp\n }\n }\n }\n': + types.GetBridgedErc721QueryDocument, "\n query getOtimismMintableERC721ByLocalTokenQuery(\n $localToken: String!\n $chainId: Int\n ) {\n optimismMintableERC721s(\n where: { localToken: $localToken, chainId: $chainId }\n ) {\n items {\n id\n chainId\n blockNumber\n localToken\n localName\n localSymbol\n remoteToken\n remoteName\n remoteSymbol\n deployer\n }\n }\n }\n": types.GetOtimismMintableErc721ByLocalTokenQueryDocument, "\n query getOtimismMintableERC721ByRemoteTokenQuery(\n $remoteToken: String!\n $chainId: Int\n ) {\n optimismMintableERC721s(\n where: { remoteToken: $remoteToken, chainId: $chainId }\n ) {\n items {\n id\n chainId\n blockNumber\n localToken\n localName\n localSymbol\n remoteToken\n remoteName\n remoteSymbol\n deployer\n }\n }\n }\n": @@ -42,6 +44,12 @@ export function graphql(source: string): unknown export function graphql( source: "\n query allOptimismMintableERC721Query($limit: Int) {\n optimismMintableERC721s(limit: $limit) {\n items {\n id\n chainId\n blockNumber\n localToken\n localName\n localSymbol\n remoteToken\n remoteName\n remoteSymbol\n deployer\n }\n }\n }\n" ): (typeof documents)["\n query allOptimismMintableERC721Query($limit: Int) {\n optimismMintableERC721s(limit: $limit) {\n items {\n id\n chainId\n blockNumber\n localToken\n localName\n localSymbol\n remoteToken\n remoteName\n remoteSymbol\n deployer\n }\n }\n }\n"] +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql( + source: '\n query getBridgedERC721Query(\n $owner: String\n $l2chainId: Int\n $states: [BridgedErc721State]\n ) {\n bridgedErc721s(\n where: { owner: $owner, l2ChainId: $l2chainId, state_in: $states }\n orderBy: "timestamp"\n orderDirection: "desc"\n ) {\n items {\n id\n state\n l1ChainId\n l2ChainId\n l1Token\n l2Token\n tokenId\n owner\n txHash\n txChainId\n timestamp\n }\n }\n }\n' +): (typeof documents)['\n query getBridgedERC721Query(\n $owner: String\n $l2chainId: Int\n $states: [BridgedErc721State]\n ) {\n bridgedErc721s(\n where: { owner: $owner, l2ChainId: $l2chainId, state_in: $states }\n orderBy: "timestamp"\n orderDirection: "desc"\n ) {\n items {\n id\n state\n l1ChainId\n l2ChainId\n l1Token\n l2Token\n tokenId\n owner\n txHash\n txChainId\n timestamp\n }\n }\n }\n'] /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/apps/website/lib/event-cache/gql/graphql.ts b/apps/website/lib/event-cache/gql/graphql.ts index 8037ea0..b9ac395 100644 --- a/apps/website/lib/event-cache/gql/graphql.ts +++ b/apps/website/lib/event-cache/gql/graphql.ts @@ -31,6 +31,135 @@ export type Scalars = { BigInt: { input: any; output: any } } +export type BridgedErc721 = { + __typename?: "BridgedErc721" + id: Scalars["String"]["output"] + l1ChainId: Scalars["Int"]["output"] + l1Token: Scalars["String"]["output"] + l2ChainId: Scalars["Int"]["output"] + l2Token: Scalars["String"]["output"] + owner: Scalars["String"]["output"] + state: BridgedErc721State + timestamp: Scalars["BigInt"]["output"] + tokenId: Scalars["String"]["output"] + txChainId: Scalars["Int"]["output"] + txHash: Scalars["String"]["output"] +} + +export type BridgedErc721Filter = { + AND?: InputMaybe>> + OR?: InputMaybe>> + id?: InputMaybe + id_contains?: InputMaybe + id_ends_with?: InputMaybe + id_in?: InputMaybe>> + id_not?: InputMaybe + id_not_contains?: InputMaybe + id_not_ends_with?: InputMaybe + id_not_in?: InputMaybe>> + id_not_starts_with?: InputMaybe + id_starts_with?: InputMaybe + l1ChainId?: InputMaybe + l1ChainId_gt?: InputMaybe + l1ChainId_gte?: InputMaybe + l1ChainId_in?: InputMaybe>> + l1ChainId_lt?: InputMaybe + l1ChainId_lte?: InputMaybe + l1ChainId_not?: InputMaybe + l1ChainId_not_in?: InputMaybe>> + l1Token?: InputMaybe + l1Token_contains?: InputMaybe + l1Token_ends_with?: InputMaybe + l1Token_in?: InputMaybe>> + l1Token_not?: InputMaybe + l1Token_not_contains?: InputMaybe + l1Token_not_ends_with?: InputMaybe + l1Token_not_in?: InputMaybe>> + l1Token_not_starts_with?: InputMaybe + l1Token_starts_with?: InputMaybe + l2ChainId?: InputMaybe + l2ChainId_gt?: InputMaybe + l2ChainId_gte?: InputMaybe + l2ChainId_in?: InputMaybe>> + l2ChainId_lt?: InputMaybe + l2ChainId_lte?: InputMaybe + l2ChainId_not?: InputMaybe + l2ChainId_not_in?: InputMaybe>> + l2Token?: InputMaybe + l2Token_contains?: InputMaybe + l2Token_ends_with?: InputMaybe + l2Token_in?: InputMaybe>> + l2Token_not?: InputMaybe + l2Token_not_contains?: InputMaybe + l2Token_not_ends_with?: InputMaybe + l2Token_not_in?: InputMaybe>> + l2Token_not_starts_with?: InputMaybe + l2Token_starts_with?: InputMaybe + owner?: InputMaybe + owner_contains?: InputMaybe + owner_ends_with?: InputMaybe + owner_in?: InputMaybe>> + owner_not?: InputMaybe + owner_not_contains?: InputMaybe + owner_not_ends_with?: InputMaybe + owner_not_in?: InputMaybe>> + owner_not_starts_with?: InputMaybe + owner_starts_with?: InputMaybe + state?: InputMaybe + state_in?: InputMaybe>> + state_not?: InputMaybe + state_not_in?: InputMaybe>> + timestamp?: InputMaybe + timestamp_gt?: InputMaybe + timestamp_gte?: InputMaybe + timestamp_in?: InputMaybe>> + timestamp_lt?: InputMaybe + timestamp_lte?: InputMaybe + timestamp_not?: InputMaybe + timestamp_not_in?: InputMaybe>> + tokenId?: InputMaybe + tokenId_contains?: InputMaybe + tokenId_ends_with?: InputMaybe + tokenId_in?: InputMaybe>> + tokenId_not?: InputMaybe + tokenId_not_contains?: InputMaybe + tokenId_not_ends_with?: InputMaybe + tokenId_not_in?: InputMaybe>> + tokenId_not_starts_with?: InputMaybe + tokenId_starts_with?: InputMaybe + txChainId?: InputMaybe + txChainId_gt?: InputMaybe + txChainId_gte?: InputMaybe + txChainId_in?: InputMaybe>> + txChainId_lt?: InputMaybe + txChainId_lte?: InputMaybe + txChainId_not?: InputMaybe + txChainId_not_in?: InputMaybe>> + txHash?: InputMaybe + txHash_contains?: InputMaybe + txHash_ends_with?: InputMaybe + txHash_in?: InputMaybe>> + txHash_not?: InputMaybe + txHash_not_contains?: InputMaybe + txHash_not_ends_with?: InputMaybe + txHash_not_in?: InputMaybe>> + txHash_not_starts_with?: InputMaybe + txHash_starts_with?: InputMaybe +} + +export type BridgedErc721Page = { + __typename?: "BridgedErc721Page" + items: Array + pageInfo: PageInfo +} + +export enum BridgedErc721State { + L1 = "L1", + L2 = "L2", + PendingToL1 = "PENDING_TO_L1", + PendingToL2 = "PENDING_TO_L2", +} + export type OptimismMintableErc721 = { __typename?: "OptimismMintableERC721" blockNumber: Scalars["BigInt"]["output"] @@ -164,10 +293,27 @@ export type PageInfo = { export type Query = { __typename?: "Query" + bridgedErc721?: Maybe + bridgedErc721s: BridgedErc721Page optimismMintableERC721?: Maybe optimismMintableERC721s: OptimismMintableErc721Page } +export type QueryBridgedErc721Args = { + id: Scalars["String"]["input"] + timestamp?: InputMaybe +} + +export type QueryBridgedErc721sArgs = { + after?: InputMaybe + before?: InputMaybe + limit?: InputMaybe + orderBy?: InputMaybe + orderDirection?: InputMaybe + timestamp?: InputMaybe + where?: InputMaybe +} + export type QueryOptimismMintableErc721Args = { id: Scalars["String"]["input"] timestamp?: InputMaybe @@ -207,6 +353,35 @@ export type AllOptimismMintableErc721QueryQuery = { } } +export type GetBridgedErc721QueryQueryVariables = Exact<{ + owner?: InputMaybe + l2chainId?: InputMaybe + states?: InputMaybe< + Array> | InputMaybe + > +}> + +export type GetBridgedErc721QueryQuery = { + __typename?: "Query" + bridgedErc721s: { + __typename?: "BridgedErc721Page" + items: Array<{ + __typename?: "BridgedErc721" + id: string + state: BridgedErc721State + l1ChainId: number + l2ChainId: number + l1Token: string + l2Token: string + tokenId: string + owner: string + txHash: string + txChainId: number + timestamp: any + }> + } +} + export type GetOtimismMintableErc721ByLocalTokenQueryQueryVariables = Exact<{ localToken: Scalars["String"]["input"] chainId?: InputMaybe @@ -350,6 +525,158 @@ export const AllOptimismMintableErc721QueryDocument = { AllOptimismMintableErc721QueryQuery, AllOptimismMintableErc721QueryQueryVariables > +export const GetBridgedErc721QueryDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "query", + name: { kind: "Name", value: "getBridgedERC721Query" }, + variableDefinitions: [ + { + kind: "VariableDefinition", + variable: { + kind: "Variable", + name: { kind: "Name", value: "owner" }, + }, + type: { kind: "NamedType", name: { kind: "Name", value: "String" } }, + }, + { + kind: "VariableDefinition", + variable: { + kind: "Variable", + name: { kind: "Name", value: "l2chainId" }, + }, + type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }, + }, + { + kind: "VariableDefinition", + variable: { + kind: "Variable", + name: { kind: "Name", value: "states" }, + }, + type: { + kind: "ListType", + type: { + kind: "NamedType", + name: { kind: "Name", value: "BridgedErc721State" }, + }, + }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "bridgedErc721s" }, + arguments: [ + { + kind: "Argument", + name: { kind: "Name", value: "where" }, + value: { + kind: "ObjectValue", + fields: [ + { + kind: "ObjectField", + name: { kind: "Name", value: "owner" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "owner" }, + }, + }, + { + kind: "ObjectField", + name: { kind: "Name", value: "l2ChainId" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "l2chainId" }, + }, + }, + { + kind: "ObjectField", + name: { kind: "Name", value: "state_in" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "states" }, + }, + }, + ], + }, + }, + { + kind: "Argument", + name: { kind: "Name", value: "orderBy" }, + value: { + kind: "StringValue", + value: "timestamp", + block: false, + }, + }, + { + kind: "Argument", + name: { kind: "Name", value: "orderDirection" }, + value: { kind: "StringValue", value: "desc", block: false }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "items" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + { kind: "Field", name: { kind: "Name", value: "state" } }, + { + kind: "Field", + name: { kind: "Name", value: "l1ChainId" }, + }, + { + kind: "Field", + name: { kind: "Name", value: "l2ChainId" }, + }, + { + kind: "Field", + name: { kind: "Name", value: "l1Token" }, + }, + { + kind: "Field", + name: { kind: "Name", value: "l2Token" }, + }, + { + kind: "Field", + name: { kind: "Name", value: "tokenId" }, + }, + { kind: "Field", name: { kind: "Name", value: "owner" } }, + { + kind: "Field", + name: { kind: "Name", value: "txHash" }, + }, + { + kind: "Field", + name: { kind: "Name", value: "txChainId" }, + }, + { + kind: "Field", + name: { kind: "Name", value: "timestamp" }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode< + GetBridgedErc721QueryQuery, + GetBridgedErc721QueryQueryVariables +> export const GetOtimismMintableErc721ByLocalTokenQueryDocument = { kind: "Document", definitions: [ diff --git a/apps/website/lib/event-cache/hooks/use-bridged-erc721.tsx b/apps/website/lib/event-cache/hooks/use-bridged-erc721.tsx new file mode 100644 index 0000000..71ca45c --- /dev/null +++ b/apps/website/lib/event-cache/hooks/use-bridged-erc721.tsx @@ -0,0 +1,50 @@ +import { graphql } from "../gql" +import { GetBridgedErc721QueryQueryVariables, InputMaybe } from "../gql/graphql" +import { useGraphQL } from "../use-graphql" + +export { BridgedErc721State } from "../gql/graphql" + +const getBridgedERC721ByOwnerQuery = graphql(/* GraphQL */ ` + query getBridgedERC721Query( + $owner: String + $l2chainId: Int + $states: [BridgedErc721State] + ) { + bridgedErc721s( + where: { owner: $owner, l2ChainId: $l2chainId, state_in: $states } + orderBy: "timestamp" + orderDirection: "desc" + ) { + items { + id + state + l1ChainId + l2ChainId + l1Token + l2Token + tokenId + owner + txHash + txChainId + timestamp + } + } + } +`) + +export function useBridgedERC721ByOwner({ + params, + query, +}: { + params: GetBridgedErc721QueryQueryVariables + query?: InputMaybe<{ enabled: boolean }> +}) { + return useGraphQL( + getBridgedERC721ByOwnerQuery, + { + queryKey: ["getBridgedERC721ByOwner", params], + enabled: query?.enabled, + }, + params + ) +} diff --git a/apps/website/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-local-token.tsx b/apps/website/lib/event-cache/hooks/use-optimism-mintable-erc721-by-local-token.tsx similarity index 92% rename from apps/website/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-local-token.tsx rename to apps/website/lib/event-cache/hooks/use-optimism-mintable-erc721-by-local-token.tsx index 42e21f5..5bea8bd 100644 --- a/apps/website/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-local-token.tsx +++ b/apps/website/lib/event-cache/hooks/use-optimism-mintable-erc721-by-local-token.tsx @@ -27,7 +27,7 @@ const getOtimismMintableERC721ByLocalTokenQuery = graphql(/* GraphQL */ ` } `) -export function useGetOtimismMintableERC721ByLocalTokenQuery(params: { +export function useOtimismMintableERC721ByLocalTokenQuery(params: { localToken: Address chainId?: number query?: { enabled: boolean } diff --git a/apps/website/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-remote-token.tsx b/apps/website/lib/event-cache/hooks/use-optimism-mintable-erc721-by-remote-token.tsx similarity index 92% rename from apps/website/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-remote-token.tsx rename to apps/website/lib/event-cache/hooks/use-optimism-mintable-erc721-by-remote-token.tsx index e8ceab3..2e5bab7 100644 --- a/apps/website/lib/event-cache/hooks/use-get-optimism-mintable-erc721-by-remote-token.tsx +++ b/apps/website/lib/event-cache/hooks/use-optimism-mintable-erc721-by-remote-token.tsx @@ -27,7 +27,7 @@ const getOtimismMintableERC721ByRemoteTokenQuery = graphql(/* GraphQL */ ` } `) -export function useGetOtimismMintableERC721ByRemoteTokenQuery(params: { +export function useOtimismMintableERC721ByRemoteTokenQuery(params: { remoteToken: Address chainId?: number query?: { enabled: boolean }