Skip to content

Commit

Permalink
Feat: bridged nfts page (#8)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
marthendalnunes authored Apr 5, 2024
1 parent 7565f66 commit adac074
Show file tree
Hide file tree
Showing 27 changed files with 1,278 additions and 388 deletions.
644 changes: 322 additions & 322 deletions apps/event-cache/abis/L1ERC721Bridge.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/event-cache/abis/L2ERC721Bridge.ts
Original file line number Diff line number Diff line change
@@ -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;
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;
2 changes: 1 addition & 1 deletion apps/event-cache/abis/OptimismMintableERC721Factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const OptimismMintableERC721Factory = [
export const OptimismMintableERC721FactoryAbi = [
{
inputs: [
{
Expand Down
3 changes: 3 additions & 0 deletions apps/event-cache/abis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./L1ERC721Bridge"
export * from "./L2ERC721Bridge"
export * from "./OptimismMintableERC721Factory"
21 changes: 21 additions & 0 deletions apps/event-cache/clients/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
3 changes: 2 additions & 1 deletion apps/event-cache/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS = "0x4200000000000000000000000000000000000017"
export const OPTIMISM_MINTABLE_ERC721_FACTORY_ADDRESS = "0x4200000000000000000000000000000000000017"
export const OPTIMISM_L2_ERC721_BRIDGE_ADDRESS = "0x4200000000000000000000000000000000000014"
74 changes: 66 additions & 8 deletions apps/event-cache/ponder.config.ts
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -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
},
},
}
},
});
14 changes: 14 additions & 0 deletions apps/event-cache/ponder.schema.ts
Original file line number Diff line number Diff line change
@@ -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(),
Expand All @@ -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(),
}),
}));
101 changes: 101 additions & 0 deletions apps/event-cache/src/L1ERC721Bridge.ts
Original file line number Diff line number Diff line change
@@ -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<string, number> = {
"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
}
});
}
});
89 changes: 89 additions & 0 deletions apps/event-cache/src/L2ERC721Bridge.ts
Original file line number Diff line number Diff line change
@@ -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
}
});
}
});
Loading

0 comments on commit adac074

Please sign in to comment.