diff --git a/.changeset/six-pillows-pump.md b/.changeset/six-pillows-pump.md new file mode 100644 index 000000000..5fa8971d8 --- /dev/null +++ b/.changeset/six-pillows-pump.md @@ -0,0 +1,7 @@ +--- +"@ensnode/ensnode-schema": minor +"@ensnode/datasources": minor +"ensindexer": minor +--- + +Added new Plugin: TokenScope. This Plugin for now will index Seaport-Sales across all other name-plugins we support (ENS, 3dns etc) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..eba1110b5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto \ No newline at end of file diff --git a/.github/scripts/promote_ensadmin.sh b/.github/scripts/promote_ensadmin.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/run_ensindexer_healthcheck.sh b/.github/scripts/run_ensindexer_healthcheck.sh old mode 100755 new mode 100644 diff --git a/apps/ensindexer/package.json b/apps/ensindexer/package.json index 88f229b3b..9658b6ab6 100644 --- a/apps/ensindexer/package.json +++ b/apps/ensindexer/package.json @@ -30,6 +30,7 @@ "@ensnode/ensrainbow-sdk": "workspace:*", "@ensnode/ponder-metadata": "workspace:*", "@ensnode/ponder-subgraph": "workspace:*", + "@opensea/seaport-js": "^4.0.5", "@hono/otel": "^0.2.2", "@hono/zod-validator": "^0.7.2", "@opentelemetry/api": "^1.9.0", diff --git a/apps/ensindexer/src/handlers/Seaport.ts b/apps/ensindexer/src/handlers/Seaport.ts new file mode 100644 index 000000000..f7a4a2d28 --- /dev/null +++ b/apps/ensindexer/src/handlers/Seaport.ts @@ -0,0 +1,302 @@ +import { Context } from "ponder:registry"; +import schema from "ponder:schema"; +import { ItemType } from "@opensea/seaport-js/lib/constants"; + +import config from "@/config"; +import { sharedEventValues, upsertAccount } from "@/lib/db-helpers"; +import { EventWithArgs } from "@/lib/ponder-helpers"; +import { + getDomainIdByTokenId, + getSupportedCurrencies, + isKnownTokenIssuingContract, +} from "@/lib/tokenscope-helpers"; +import { NameSoldInsert, TokenTypes } from "@ensnode/ensnode-schema"; +import { ChainId, uint256ToHex32 } from "@ensnode/ensnode-sdk"; +import { Address, Hex, zeroAddress } from "viem"; + +type OfferItem = { + /** + * The type of item in the offer. + * For example, ERC20, ERC721, ERC1155, or NATIVE (ETH) + */ + itemType: ItemType; + + /** + * The contract address of the token. + * - For ERC721/ERC1155: The NFT contract address + * - For ERC20: The token contract address + * - For NATIVE (ETH): Zero address (0x0000000000000000000000000000000000000000) + */ + token: Address; + + /** + * The identifier field has different meanings based on itemType: + * - For ERC721/ERC1155: The specific token ID of the NFT + * - For ERC20: Always 0 (not used for fungible tokens) + * - For NATIVE (ETH): Always 0 (not used for native currency) + */ + identifier: bigint; + + /** + * The amount field has different meanings based on itemType: + * - For ERC721: Always 1 (you can only transfer 1 unique NFT) + * - For ERC1155: The quantity of tokens with the specified identifier (for our purposes, always 1) + * - For ERC20: The amount of tokens (in wei/smallest unit) + * - For NATIVE (ETH): The amount of ETH (in wei) + */ + amount: bigint; +}; + +type ConsiderationItem = { + /** + * The type of item in the consideration. + * For example, ERC20, ERC721, ERC1155, or NATIVE (ETH) + */ + itemType: ItemType; + + /** + * The contract address of the token. + * - For ERC721/ERC1155: The NFT contract address + * - For ERC20: The token contract address + * - For NATIVE (ETH): Zero address (0x0000000000000000000000000000000000000000) + */ + token: Address; + + /** + * The identifier field has different meanings based on itemType: + * - For ERC721/ERC1155: The specific token ID of the NFT + * - For ERC20: Always 0 (not used for fungible tokens) + * - For NATIVE (ETH): Always 0 (not used for native currency) + */ + identifier: bigint; + + /** + * The amount field has different meanings based on itemType: + * - For ERC721: Always 1 (you can only transfer 1 unique NFT) + * - For ERC1155: The quantity of tokens with the specified identifier + * - For ERC20: The amount of tokens (in wei/smallest unit) + * - For NATIVE (ETH): The amount of ETH (in wei) + */ + amount: bigint; + + /** + * The address that receives the consideration items from the order. + * This is typically the order fulfiller or their designated recipient. + */ + recipient: Address; +}; + +interface SeaportOrderFulfilledEvent + extends EventWithArgs<{ + /** + * The unique hash identifier of the fulfilled order. + * Used to track and reference specific orders on-chain. + */ + orderHash: Hex; + + /** + * The address of the account that created and signed the original order. + * This is the party offering items for trade. + */ + offerer: Address; + + /** + * The address of the zone contract that implements custom validation rules. + * Zones can enforce additional restrictions like allowlists, time windows, + * or other custom logic before order fulfillment. Can be zero address if + * no additional validation is required. + */ + zone: Address; + + /** + * The address that receives the offered items from the order. + * This is typically the order fulfiller or their designated recipient. + */ + recipient: Address; + + /** + * Array of items that the offerer is giving up in this order. + * For listings: NFTs/tokens being sold + * For offers: ETH/ERC20 tokens being offered as payment + */ + offer: readonly OfferItem[]; + + /** + * Array of items that the offerer expects to receive in return. + * For listings: ETH/ERC20 tokens expected as payment + * For offers: NFTs/tokens being requested in exchange + */ + consideration: readonly ConsiderationItem[]; + }> {} + +/** + * Gets the currency symbol for a given address on a specific chain. + */ +function getCurrencySymbol(chainId: number, currencyAddress: Address): string | null { + const supportedCurrencies = getSupportedCurrencies(chainId); + + if (currencyAddress === zeroAddress) { + const ethCurrency = supportedCurrencies.find((currency) => currency.address === null); + return ethCurrency?.symbol || null; + } + + const matchingCurrency = supportedCurrencies.find( + (currency) => + currency.address && currency.address.toLowerCase() === currencyAddress.toLowerCase(), + ); + + return matchingCurrency?.symbol || null; +} + +/** + * Checks if an item is a supported NFT (ERC721/ERC1155 from known contracts) + */ +function isSupportedNFT(chainId: number, item: OfferItem | ConsiderationItem): boolean { + const isValidItemType = item.itemType === ItemType.ERC721 || item.itemType === ItemType.ERC1155; + const isSupportedContract = isKnownTokenIssuingContract(config.namespace, { + chainId, + address: item.token, + }); + + return isValidItemType && isSupportedContract; +} + +/** + * Checks if an item is a payment token (ETH or ERC20) + */ +function isPaymentToken(item: OfferItem | ConsiderationItem): boolean { + return item.itemType === ItemType.NATIVE || item.itemType === ItemType.ERC20; +} + +/** + * Determines if a Seaport order fulfillment represents an indexable sale + * and extracts the sale data if so. + */ +function getSaleIndexable( + context: Context, + event: SeaportOrderFulfilledEvent, + chainId: ChainId, +): NameSoldInsert | null { + const { offer, consideration, orderHash, offerer, recipient } = event.args; + + // Find all NFTs and payment items + const nftsInOffer = offer.filter((item) => isSupportedNFT(chainId, item)); + const nftsInConsideration = consideration.filter((item) => isSupportedNFT(chainId, item)); + const paymentsInOffer = offer.filter(isPaymentToken); + const paymentsInConsideration = consideration.filter(isPaymentToken); + + let nftItem: OfferItem | ConsiderationItem; + let paymentItems: (OfferItem | ConsiderationItem)[]; + let seller: Address; + let buyer: Address; + + // Determine transaction type and validate structure + if ( + nftsInOffer.length === 1 && + nftsInConsideration.length === 0 && + paymentsInConsideration.length > 0 + ) { + // Listing: NFT in offer, payment in consideration + nftItem = nftsInOffer[0]!; + paymentItems = paymentsInConsideration; + seller = offerer; + buyer = recipient; + } else if ( + nftsInConsideration.length === 1 && + nftsInOffer.length === 0 && + paymentsInOffer.length > 0 + ) { + // Offer: payment in offer, NFT in consideration + nftItem = nftsInConsideration[0]!; + paymentItems = paymentsInOffer; + seller = recipient; + buyer = offerer; + } else { + // Invalid structure + return null; + } + + // Validate payment structure + if (paymentItems.length === 0) { + return null; + } + + // Check for mixed currencies + const paymentTokens = paymentItems.map((item) => item.token.toLowerCase()); + const uniqueTokens = [...new Set(paymentTokens)]; + if (uniqueTokens.length > 1) { + return null; // Mixed currencies not supported + } + + const currencyAddress = paymentItems[0]!.token; + const currencySymbol = getCurrencySymbol(chainId, currencyAddress); + if (!currencySymbol) { + return null; // Unsupported currency + } + + // Calculate total payment amount + const totalAmount = paymentItems.reduce((total, item) => total + item.amount, 0n); + if (totalAmount <= 0n) { + return null; + } + + // Extract NFT details + const contractAddress = nftItem.token; + const tokenId = nftItem.identifier.toString(); + const tokenIdHex = uint256ToHex32(BigInt(tokenId)); + + // Get domain ID + let domainId; + try { + domainId = getDomainIdByTokenId(chainId, config.namespace, contractAddress, tokenIdHex); + } catch (e) { + // should we log here? + return null; + } + + return { + ...sharedEventValues(context.chain.id, event), + logIndex: event.log.logIndex, + chainId, + orderHash, + timestamp: event.block.timestamp, + fromOwnerId: seller, + newOwnerId: buyer, + contractAddress: contractAddress, + tokenId: tokenId, + tokenType: nftItem.itemType === ItemType.ERC721 ? TokenTypes.ERC721 : TokenTypes.ERC1155, + domainId, + currency: currencySymbol, + price: totalAmount, + }; +} + +/** + * Processes a validated sale transaction + */ +async function handleSale(context: Context, saleData: NameSoldInsert): Promise { + // Ensure accounts exist + await upsertAccount(context, saleData.fromOwnerId); + await upsertAccount(context, saleData.newOwnerId); + + // Record the sale + await context.db.insert(schema.nameSold).values(saleData); +} + +/** + * Main handler for Seaport OrderFulfilled events + */ +export async function handleOrderFulfilled({ + context, + event, +}: { + context: Context; + event: SeaportOrderFulfilledEvent; +}) { + const chainId = context.chain.id; + + const indexableSale = getSaleIndexable(context, event, chainId); + if (indexableSale) { + await handleSale(context, indexableSale); + } +} diff --git a/apps/ensindexer/src/lib/tokenscope-helpers.ts b/apps/ensindexer/src/lib/tokenscope-helpers.ts new file mode 100644 index 000000000..721367191 --- /dev/null +++ b/apps/ensindexer/src/lib/tokenscope-helpers.ts @@ -0,0 +1,397 @@ +import { + DatasourceNames, + ENSNamespaceId, + ENSNamespaceIds, + getDatasource, +} from "@ensnode/datasources"; +import {BASE_NODE, ChainId, ETH_NODE, makeSubdomainNode} from "@ensnode/ensnode-sdk"; +import { Address, Hex } from "viem"; +import { + base, + baseSepolia, + holesky, + linea, + lineaSepolia, + mainnet, + optimism, + sepolia, +} from "viem/chains"; + +/** + * Identifies a specific address on a specific chain. + */ +export interface ChainAddress { + chainId: ChainId; + address: Address; +} + +/** + * Identifies a specific currency. + */ +export interface Currency { + symbol: string; + name: string; + decimals: number; + // For native currencies, address will be null + address: Address | null; +} + +/** + * Identifies a specific currency on a specific chain. + */ +export interface ChainCurrency extends Currency { + chainId: ChainId; +} + +/** + * Returns an array of 0 or more ChainAddress objects that are known to provide tokenized name ownership. + * + * @param namespaceId - The ENSNamespace identifier (e.g. 'mainnet', 'sepolia', 'holesky', 'ens-test-env') + * @returns an array of 0 or more ChainAddress objects + */ +export const getKnownTokenIssuingContracts = (namespaceId: ENSNamespaceId): ChainAddress[] => { + switch (namespaceId) { + case ENSNamespaceIds.Mainnet: { + const rootDatasource = getDatasource(namespaceId, DatasourceNames.ENSRoot); + const lineanamesDatasource = getDatasource(namespaceId, DatasourceNames.Lineanames); + const basenamesDatasource = getDatasource(namespaceId, DatasourceNames.Basenames); + const threeDnsBaseDatasource = getDatasource(namespaceId, DatasourceNames.ThreeDNSBase); + const threeDnsOptimismDatasource = getDatasource( + namespaceId, + DatasourceNames.ThreeDNSOptimism, + ); + return [ + // Eth Token - Mainnet + { + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["BaseRegistrar"].address, + }, + // NameWrapper Token - Mainnet + { + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["NameWrapper"].address, + }, + // 3DNS Token - Optimism + { + chainId: threeDnsOptimismDatasource.chain.id, + address: threeDnsOptimismDatasource.contracts["ThreeDNSToken"].address, + }, + // 3DNS Token - Base + { + chainId: threeDnsBaseDatasource.chain.id, + address: threeDnsBaseDatasource.contracts["ThreeDNSToken"].address, + }, + // Linea Names Token - Linea + { + chainId: lineanamesDatasource.chain.id, + address: lineanamesDatasource.contracts["BaseRegistrar"].address, + }, + // Base Names Token - Base + { + chainId: basenamesDatasource.chain.id, + address: basenamesDatasource.contracts["BaseRegistrar"].address, + }, + ]; + } + case ENSNamespaceIds.Sepolia: { + const rootDatasource = getDatasource(namespaceId, DatasourceNames.ENSRoot); + const basenamesDatasource = getDatasource(namespaceId, DatasourceNames.Basenames); + const lineanamesDatasource = getDatasource(namespaceId, DatasourceNames.Lineanames); + + return [ + { + // ENS Token - Sepolia + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["BaseRegistrar"].address, + }, + { + // NameWrapper Token - Sepolia + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["NameWrapper"].address, + }, + { + // Basenames Token - Base Sepolia + chainId: basenamesDatasource.chain.id, + address: basenamesDatasource.contracts["BaseRegistrar"].address, + }, + { + // Lineanames Token - Linea Sepolia + chainId: lineanamesDatasource.chain.id, + address: lineanamesDatasource.contracts["BaseRegistrar"].address, + }, + ]; + } + case ENSNamespaceIds.Holesky: { + const rootDatasource = getDatasource(namespaceId, DatasourceNames.ENSRoot); + return [ + { + // ENS Token - Holesky + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["BaseRegistrar"].address, + }, + { + // NameWrapper Token - Holesky + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["NameWrapper"].address, + }, + ]; + } + case ENSNamespaceIds.EnsTestEnv: { + const rootDatasource = getDatasource(namespaceId, DatasourceNames.ENSRoot); + return [ + { + // ENS Token - EnsTestEnv + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["BaseRegistrar"].address, + }, + { + // NameWrapper Token - EnsTestEnv + chainId: rootDatasource.chain.id, + address: rootDatasource.contracts["NameWrapper"].address, + }, + ]; + } + } +}; + +/** + * Returns a boolean indicating whether the provided ChainAddress is a known token issuing contract. + * + * @param namespaceId - The ENSNamespace identifier (e.g. 'mainnet', 'sepolia', 'holesky', 'ens-test-env') + * @param chainAddress - The ChainAddress to check + * @returns a boolean indicating whether the provided ChainAddress is a known token issuing contract + */ +export const isKnownTokenIssuingContract = ( + namespaceId: ENSNamespaceId, + chainAddress: ChainAddress, +): boolean => { + const knownContracts = getKnownTokenIssuingContracts(namespaceId); + return knownContracts.some((contract) => isEqualChainAddress(contract, chainAddress)); +}; + +/** + * Returns a boolean indicating whether the provided ChainAddress objects are equal. + * + * @param address1 - The first ChainAddress to compare + * @param address2 - The second ChainAddress to compare + * @returns a boolean indicating whether the provided ChainAddress objects are equal + */ +export const isEqualChainAddress = (address1: ChainAddress, address2: ChainAddress): boolean => { + return ( + address1.chainId === address2.chainId && + address1.address.toLowerCase() === address2.address.toLowerCase() + ); +}; + +/** + * Get the domainId by contract address and tokenId + * @param chainId - The chainId of the NFT + * @param namespaceId - The ENSNamespace identifier (e.g. 'mainnet', 'sepolia', 'holesky', 'ens-test-env') + * @param contractAddress - contract address of the NFT + * @param tokenIdHex - tokenId of the NFT in hex + */ +export function getDomainIdByTokenId( + chainId: ChainId, + namespaceId: ENSNamespaceId, + contractAddress: Address, + tokenIdHex: Hex, +): Hex { + const ensDataSource = getDatasource(namespaceId, DatasourceNames.ENSRoot); + if (ensDataSource.chain.id !== chainId) { + throw new Error(`Namespace ${namespaceId} is not deployed on chain ${chainId}`); + } + const baseRegistrarContractAddress = ensDataSource.contracts["BaseRegistrar"].address; + + // OLD ENS Registry: tokenId is labelhash so need to convert to namehash + if (contractAddress === baseRegistrarContractAddress) { + return makeSubdomainNode(tokenIdHex, ETH_NODE); + } + + const baseNamesDataSource = getDatasource(namespaceId, DatasourceNames.Basenames); + if (baseNamesDataSource.chain.id !== chainId) { + throw new Error(`Namespace ${namespaceId} is not deployed on chain ${chainId}`); + } + const basenamesContractAddress = baseNamesDataSource.contracts["BaseRegistrar"].address; + + // basenames: tokenId is labelhash so need to convert to namehash + if (contractAddress === basenamesContractAddress) { + return makeSubdomainNode(tokenIdHex, BASE_NODE); + } + + // 3dns token id is already derived from namehash + // linea token id is already derived from namehash + return tokenIdHex; +} + +// Well-known currencies +const ETH_CURRENCY = { + symbol: "ETH", + name: "Ethereum", + decimals: 18, + address: null, +} as const; + +const CHAIN_CURRENCIES = { + // Mainnet + [mainnet.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0xA0b86a33E6417c5Dd4Baf8C54e5de49E293E9169" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0x6B175474E89094C44Da98b954EedeAC495271d0F" as Address, + }, + ], + // Base + [base.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb" as Address, + }, + ], + // Optimism + [optimism.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1" as Address, + }, + ], + // Linea + [linea.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5" as Address, + }, + ], + // Sepolia + [sepolia.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6" as Address, + }, + ], + // Holesky + [holesky.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6" as Address, + }, + ], + // Base Sepolia + [baseSepolia.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0x7368C6C68a4b2b68F90DB2e8F5E3b8E1E5e4F5c7" as Address, + }, + ], + // Linea Sepolia + [lineaSepolia.id]: [ + { + symbol: "USDC", + name: "USD Coin", + decimals: 6, + address: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff" as Address, + }, + { + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + address: "0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5" as Address, + }, + ], +} as const; + +/** + * Returns an array of supported currencies for a given chain ID. + * + * @param chainId - The chain ID to get supported currencies for + * @returns an array of ChainCurrency objects representing supported currencies on the chain + */ +export const getSupportedCurrencies = (chainId: ChainId): ChainCurrency[] => { + const chainCurrencies = CHAIN_CURRENCIES[chainId as keyof typeof CHAIN_CURRENCIES] || []; + + // Always add ETH as the native currency + const currencies: ChainCurrency[] = [ + { + ...ETH_CURRENCY, + chainId, + }, + ]; + + // Add chain-specific currencies + currencies.push( + ...chainCurrencies.map((currency) => ({ + ...currency, + chainId, + })), + ); + + return currencies; +}; + +/** + * Returns a boolean indicating whether the provided address is a known supported currency contract. + * + * @param chainId - The chain ID + * @param address - The contract address to check + * @returns a boolean indicating whether the address is a known supported currency contract + */ +export const isKnownCurrencyContract = (chainId: ChainId, address: Address): boolean => { + const supportedCurrencies = getSupportedCurrencies(chainId); + return supportedCurrencies.some( + (currency) => currency.address && currency.address.toLowerCase() === address.toLowerCase(), + ); +}; diff --git a/apps/ensindexer/src/plugins/index.ts b/apps/ensindexer/src/plugins/index.ts index be238d8a5..bec622776 100644 --- a/apps/ensindexer/src/plugins/index.ts +++ b/apps/ensindexer/src/plugins/index.ts @@ -7,12 +7,14 @@ import referralsPlugin from "./referrals/plugin"; import reverseResolversPlugin from "./reverse-resolvers/plugin"; import subgraphPlugin from "./subgraph/plugin"; import threednsPlugin from "./threedns/plugin"; +import tokenScopePlugin from "./tokenscope/plugin"; export const ALL_PLUGINS = [ subgraphPlugin, basenamesPlugin, lineaNamesPlugin, threednsPlugin, + tokenScopePlugin, reverseResolversPlugin, referralsPlugin, ] as const; diff --git a/apps/ensindexer/src/plugins/referrals/handlers/UnwrappedEthRegistrarController.ts b/apps/ensindexer/src/plugins/referrals/handlers/UnwrappedEthRegistrarController.ts index 525838996..268f27723 100644 --- a/apps/ensindexer/src/plugins/referrals/handlers/UnwrappedEthRegistrarController.ts +++ b/apps/ensindexer/src/plugins/referrals/handlers/UnwrappedEthRegistrarController.ts @@ -1,11 +1,8 @@ import { ponder } from "ponder:registry"; -import { namehash } from "viem"; import { handleRegistrationReferral, handleRenewalReferral } from "@/handlers/Referrals"; import { namespaceContract } from "@/lib/plugin-helpers"; -import { PluginName, makeSubdomainNode } from "@ensnode/ensnode-sdk"; - -const ETH_NODE = namehash("eth"); +import { ETH_NODE, PluginName, makeSubdomainNode } from "@ensnode/ensnode-sdk"; /** * Registers event handlers with Ponder. diff --git a/apps/ensindexer/src/plugins/tokenscope/event-handlers.ts b/apps/ensindexer/src/plugins/tokenscope/event-handlers.ts new file mode 100644 index 000000000..f48928573 --- /dev/null +++ b/apps/ensindexer/src/plugins/tokenscope/event-handlers.ts @@ -0,0 +1,9 @@ +import config from "@/config"; +import { PluginName } from "@ensnode/ensnode-sdk"; + +import attach_Seaport from "./handlers/Seaport"; + +// conditionally attach event handlers when Ponder executes this file +if (config.plugins.includes(PluginName.TokenScope)) { + attach_Seaport(); +} diff --git a/apps/ensindexer/src/plugins/tokenscope/handlers/Seaport.ts b/apps/ensindexer/src/plugins/tokenscope/handlers/Seaport.ts new file mode 100644 index 000000000..88b3db297 --- /dev/null +++ b/apps/ensindexer/src/plugins/tokenscope/handlers/Seaport.ts @@ -0,0 +1,15 @@ +import { ponder } from "ponder:registry"; + +import { PluginName } from "@ensnode/ensnode-sdk"; + +import { handleOrderFulfilled } from "@/handlers/Seaport"; +import { namespaceContract } from "@/lib/plugin-helpers"; + +/** + * Registers event handlers with Ponder. + */ +export default function () { + const pluginName = PluginName.TokenScope; + + ponder.on(namespaceContract(pluginName, "Seaport:OrderFulfilled"), handleOrderFulfilled); +} diff --git a/apps/ensindexer/src/plugins/tokenscope/plugin.ts b/apps/ensindexer/src/plugins/tokenscope/plugin.ts new file mode 100644 index 000000000..752b710ae --- /dev/null +++ b/apps/ensindexer/src/plugins/tokenscope/plugin.ts @@ -0,0 +1,44 @@ +/** + * The TokenScope plugin describes indexing behavior for marketplace contracts (e.g. Seaport) on all supported networks. + */ + +import { + createPlugin, + getDatasourceAsFullyDefinedAtCompileTime, + namespaceContract, +} from "@/lib/plugin-helpers"; +import { chainConfigForContract, chainsConnectionConfig } from "@/lib/ponder-helpers"; +import { DatasourceNames } from "@ensnode/datasources"; +import { PluginName } from "@ensnode/ensnode-sdk"; +import * as ponder from "ponder"; + +const pluginName = PluginName.TokenScope; + +export default createPlugin({ + name: pluginName, + requiredDatasourceNames: [DatasourceNames.Seaport], + createPonderConfig(config) { + const seaport = getDatasourceAsFullyDefinedAtCompileTime( + config.namespace, + DatasourceNames.Seaport, + ); + + return ponder.createConfig({ + chains: { + ...chainsConnectionConfig(config.rpcConfigs, seaport.chain.id), + }, + contracts: { + [namespaceContract(pluginName, "Seaport")]: { + chain: { + ...chainConfigForContract( + config.globalBlockrange, + seaport.chain.id, + seaport.contracts.Seaport, + ), + }, + abi: seaport.contracts.Seaport.abi, + }, + }, + }); + }, +}); diff --git a/apps/ensrainbow/download-rainbow-tables.sh b/apps/ensrainbow/download-rainbow-tables.sh old mode 100755 new mode 100644 diff --git a/apps/ensrainbow/scripts/download-ensrainbow-files.sh b/apps/ensrainbow/scripts/download-ensrainbow-files.sh old mode 100755 new mode 100644 diff --git a/packages/datasources/package.json b/packages/datasources/package.json index fd70edca8..49edc0276 100644 --- a/packages/datasources/package.json +++ b/packages/datasources/package.json @@ -47,6 +47,7 @@ "viem": "catalog:" }, "dependencies": { + "@ensnode/ensnode-sdk": "workspace:*", "@ponder/utils": "catalog:" } } diff --git a/packages/datasources/src/abis/seaport/Seaport.ts b/packages/datasources/src/abis/seaport/Seaport.ts new file mode 100644 index 000000000..6d4e14858 --- /dev/null +++ b/packages/datasources/src/abis/seaport/Seaport.ts @@ -0,0 +1,2002 @@ +export const Seaport = [ + { + inputs: [{ internalType: "address", name: "conduitController", type: "address" }], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "BadContractSignature", type: "error" }, + { + inputs: [], + name: "BadFraction", + type: "error", + }, + { + inputs: [ + { internalType: "address", name: "token", type: "address" }, + { + internalType: "address", + name: "from", + type: "address", + }, + { internalType: "address", name: "to", type: "address" }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "BadReturnValueFromERC20OnTransfer", + type: "error", + }, + { + inputs: [{ internalType: "uint8", name: "v", type: "uint8" }], + name: "BadSignatureV", + type: "error", + }, + { inputs: [], name: "CannotCancelOrder", type: "error" }, + { + inputs: [], + name: "ConsiderationCriteriaResolverOutOfRange", + type: "error", + }, + { + inputs: [], + name: "ConsiderationLengthNotEqualToTotalOriginal", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "orderIndex", type: "uint256" }, + { + internalType: "uint256", + name: "considerationIndex", + type: "uint256", + }, + { internalType: "uint256", name: "shortfallAmount", type: "uint256" }, + ], + name: "ConsiderationNotMet", + type: "error", + }, + { inputs: [], name: "CriteriaNotEnabledForItem", type: "error" }, + { + inputs: [ + { + internalType: "address", + name: "token", + type: "address", + }, + { internalType: "address", name: "from", type: "address" }, + { + internalType: "address", + name: "to", + type: "address", + }, + { internalType: "uint256[]", name: "identifiers", type: "uint256[]" }, + { + internalType: "uint256[]", + name: "amounts", + type: "uint256[]", + }, + ], + name: "ERC1155BatchTransferGenericFailure", + type: "error", + }, + { inputs: [], name: "InexactFraction", type: "error" }, + { + inputs: [], + name: "InsufficientNativeTokensSupplied", + type: "error", + }, + { inputs: [], name: "Invalid1155BatchTransferEncoding", type: "error" }, + { + inputs: [], + name: "InvalidBasicOrderParameterEncoding", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "conduit", type: "address" }], + name: "InvalidCallToConduit", + type: "error", + }, + { + inputs: [ + { internalType: "bytes32", name: "conduitKey", type: "bytes32" }, + { + internalType: "address", + name: "conduit", + type: "address", + }, + ], + name: "InvalidConduit", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + name: "InvalidContractOrder", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "amount", type: "uint256" }], + name: "InvalidERC721TransferAmount", + type: "error", + }, + { inputs: [], name: "InvalidFulfillmentComponentData", type: "error" }, + { + inputs: [ + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "InvalidMsgValue", + type: "error", + }, + { inputs: [], name: "InvalidNativeOfferItem", type: "error" }, + { + inputs: [], + name: "InvalidProof", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + name: "InvalidRestrictedOrder", + type: "error", + }, + { inputs: [], name: "InvalidSignature", type: "error" }, + { + inputs: [], + name: "InvalidSigner", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "startTime", type: "uint256" }, + { + internalType: "uint256", + name: "endTime", + type: "uint256", + }, + ], + name: "InvalidTime", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "fulfillmentIndex", type: "uint256" }], + name: "MismatchedFulfillmentOfferAndConsiderationComponents", + type: "error", + }, + { + inputs: [{ internalType: "enum Side", name: "side", type: "uint8" }], + name: "MissingFulfillmentComponentOnAggregation", + type: "error", + }, + { inputs: [], name: "MissingItemAmount", type: "error" }, + { + inputs: [], + name: "MissingOriginalConsiderationItems", + type: "error", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "NativeTokenTransferGenericFailure", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "NoContract", + type: "error", + }, + { inputs: [], name: "NoReentrantCalls", type: "error" }, + { + inputs: [], + name: "NoSpecifiedOrdersAvailable", + type: "error", + }, + { inputs: [], name: "OfferAndConsiderationRequiredOnFulfillment", type: "error" }, + { + inputs: [], + name: "OfferCriteriaResolverOutOfRange", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + name: "OrderAlreadyFilled", + type: "error", + }, + { + inputs: [{ internalType: "enum Side", name: "side", type: "uint8" }], + name: "OrderCriteriaResolverOutOfRange", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + name: "OrderIsCancelled", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + name: "OrderPartiallyFilled", + type: "error", + }, + { inputs: [], name: "PartialFillsNotEnabledForOrder", type: "error" }, + { + inputs: [ + { + internalType: "address", + name: "token", + type: "address", + }, + { internalType: "address", name: "from", type: "address" }, + { + internalType: "address", + name: "to", + type: "address", + }, + { internalType: "uint256", name: "identifier", type: "uint256" }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "TokenTransferGenericFailure", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "orderIndex", type: "uint256" }, + { + internalType: "uint256", + name: "considerationIndex", + type: "uint256", + }, + ], + name: "UnresolvedConsiderationCriteria", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "orderIndex", type: "uint256" }, + { + internalType: "uint256", + name: "offerIndex", + type: "uint256", + }, + ], + name: "UnresolvedOfferCriteria", + type: "error", + }, + { inputs: [], name: "UnusedItemParameters", type: "error" }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "uint256", name: "newCounter", type: "uint256" }, + { + indexed: true, + internalType: "address", + name: "offerer", + type: "address", + }, + ], + name: "CounterIncremented", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "bytes32", name: "orderHash", type: "bytes32" }, + { + indexed: true, + internalType: "address", + name: "offerer", + type: "address", + }, + { indexed: true, internalType: "address", name: "zone", type: "address" }, + ], + name: "OrderCancelled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "bytes32", name: "orderHash", type: "bytes32" }, + { + indexed: true, + internalType: "address", + name: "offerer", + type: "address", + }, + { indexed: true, internalType: "address", name: "zone", type: "address" }, + { + indexed: false, + internalType: "address", + name: "recipient", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifier", + type: "uint256", + }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + indexed: false, + internalType: "struct SpentItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifier", + type: "uint256", + }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { + internalType: "address payable", + name: "recipient", + type: "address", + }, + ], + indexed: false, + internalType: "struct ReceivedItem[]", + name: "consideration", + type: "tuple[]", + }, + ], + name: "OrderFulfilled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "bytes32", name: "orderHash", type: "bytes32" }, + { + components: [ + { internalType: "address", name: "offerer", type: "address" }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + indexed: false, + internalType: "struct OrderParameters", + name: "orderParameters", + type: "tuple", + }, + ], + name: "OrderValidated", + type: "event", + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: "bytes32[]", name: "orderHashes", type: "bytes32[]" }], + name: "OrdersMatched", + type: "event", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "offerer", type: "address" }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "counter", type: "uint256" }, + ], + internalType: "struct OrderComponents[]", + name: "orders", + type: "tuple[]", + }, + ], + name: "cancel", + outputs: [{ internalType: "bool", name: "cancelled", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "offerer", + type: "address", + }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + internalType: "struct OrderParameters", + name: "parameters", + type: "tuple", + }, + { internalType: "uint120", name: "numerator", type: "uint120" }, + { + internalType: "uint120", + name: "denominator", + type: "uint120", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { + internalType: "bytes", + name: "extraData", + type: "bytes", + }, + ], + internalType: "struct AdvancedOrder", + name: "", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256", + name: "orderIndex", + type: "uint256", + }, + { internalType: "enum Side", name: "side", type: "uint8" }, + { + internalType: "uint256", + name: "index", + type: "uint256", + }, + { internalType: "uint256", name: "identifier", type: "uint256" }, + { + internalType: "bytes32[]", + name: "criteriaProof", + type: "bytes32[]", + }, + ], + internalType: "struct CriteriaResolver[]", + name: "", + type: "tuple[]", + }, + { internalType: "bytes32", name: "fulfillerConduitKey", type: "bytes32" }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + ], + name: "fulfillAdvancedOrder", + outputs: [{ internalType: "bool", name: "fulfilled", type: "bool" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "offerer", + type: "address", + }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + internalType: "struct OrderParameters", + name: "parameters", + type: "tuple", + }, + { internalType: "uint120", name: "numerator", type: "uint120" }, + { + internalType: "uint120", + name: "denominator", + type: "uint120", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { + internalType: "bytes", + name: "extraData", + type: "bytes", + }, + ], + internalType: "struct AdvancedOrder[]", + name: "", + type: "tuple[]", + }, + { + components: [ + { + internalType: "uint256", + name: "orderIndex", + type: "uint256", + }, + { internalType: "enum Side", name: "side", type: "uint8" }, + { + internalType: "uint256", + name: "index", + type: "uint256", + }, + { internalType: "uint256", name: "identifier", type: "uint256" }, + { + internalType: "bytes32[]", + name: "criteriaProof", + type: "bytes32[]", + }, + ], + internalType: "struct CriteriaResolver[]", + name: "", + type: "tuple[]", + }, + { + components: [ + { internalType: "uint256", name: "orderIndex", type: "uint256" }, + { + internalType: "uint256", + name: "itemIndex", + type: "uint256", + }, + ], + internalType: "struct FulfillmentComponent[][]", + name: "", + type: "tuple[][]", + }, + { + components: [ + { internalType: "uint256", name: "orderIndex", type: "uint256" }, + { + internalType: "uint256", + name: "itemIndex", + type: "uint256", + }, + ], + internalType: "struct FulfillmentComponent[][]", + name: "", + type: "tuple[][]", + }, + { internalType: "bytes32", name: "fulfillerConduitKey", type: "bytes32" }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { internalType: "uint256", name: "maximumFulfilled", type: "uint256" }, + ], + name: "fulfillAvailableAdvancedOrders", + outputs: [ + { + internalType: "bool[]", + name: "", + type: "bool[]", + }, + { + components: [ + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifier", + type: "uint256", + }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { + internalType: "address payable", + name: "recipient", + type: "address", + }, + ], + internalType: "struct ReceivedItem", + name: "item", + type: "tuple", + }, + { internalType: "address", name: "offerer", type: "address" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + ], + internalType: "struct Execution[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "offerer", + type: "address", + }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + internalType: "struct OrderParameters", + name: "parameters", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct Order[]", + name: "", + type: "tuple[]", + }, + { + components: [ + { internalType: "uint256", name: "orderIndex", type: "uint256" }, + { + internalType: "uint256", + name: "itemIndex", + type: "uint256", + }, + ], + internalType: "struct FulfillmentComponent[][]", + name: "", + type: "tuple[][]", + }, + { + components: [ + { internalType: "uint256", name: "orderIndex", type: "uint256" }, + { + internalType: "uint256", + name: "itemIndex", + type: "uint256", + }, + ], + internalType: "struct FulfillmentComponent[][]", + name: "", + type: "tuple[][]", + }, + { internalType: "bytes32", name: "fulfillerConduitKey", type: "bytes32" }, + { + internalType: "uint256", + name: "maximumFulfilled", + type: "uint256", + }, + ], + name: "fulfillAvailableOrders", + outputs: [ + { + internalType: "bool[]", + name: "", + type: "bool[]", + }, + { + components: [ + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifier", + type: "uint256", + }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { + internalType: "address payable", + name: "recipient", + type: "address", + }, + ], + internalType: "struct ReceivedItem", + name: "item", + type: "tuple", + }, + { internalType: "address", name: "offerer", type: "address" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + ], + internalType: "struct Execution[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "considerationToken", + type: "address", + }, + { + internalType: "uint256", + name: "considerationIdentifier", + type: "uint256", + }, + { + internalType: "uint256", + name: "considerationAmount", + type: "uint256", + }, + { internalType: "address payable", name: "offerer", type: "address" }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { internalType: "address", name: "offerToken", type: "address" }, + { + internalType: "uint256", + name: "offerIdentifier", + type: "uint256", + }, + { + internalType: "uint256", + name: "offerAmount", + type: "uint256", + }, + { + internalType: "enum BasicOrderType", + name: "basicOrderType", + type: "uint8", + }, + { internalType: "uint256", name: "startTime", type: "uint256" }, + { + internalType: "uint256", + name: "endTime", + type: "uint256", + }, + { internalType: "bytes32", name: "zoneHash", type: "bytes32" }, + { + internalType: "uint256", + name: "salt", + type: "uint256", + }, + { internalType: "bytes32", name: "offererConduitKey", type: "bytes32" }, + { + internalType: "bytes32", + name: "fulfillerConduitKey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "totalOriginalAdditionalRecipients", + type: "uint256", + }, + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct AdditionalRecipient[]", + name: "additionalRecipients", + type: "tuple[]", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct BasicOrderParameters", + name: "parameters", + type: "tuple", + }, + ], + name: "fulfillBasicOrder", + outputs: [{ internalType: "bool", name: "fulfilled", type: "bool" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "considerationToken", + type: "address", + }, + { + internalType: "uint256", + name: "considerationIdentifier", + type: "uint256", + }, + { + internalType: "uint256", + name: "considerationAmount", + type: "uint256", + }, + { internalType: "address payable", name: "offerer", type: "address" }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { internalType: "address", name: "offerToken", type: "address" }, + { + internalType: "uint256", + name: "offerIdentifier", + type: "uint256", + }, + { + internalType: "uint256", + name: "offerAmount", + type: "uint256", + }, + { + internalType: "enum BasicOrderType", + name: "basicOrderType", + type: "uint8", + }, + { internalType: "uint256", name: "startTime", type: "uint256" }, + { + internalType: "uint256", + name: "endTime", + type: "uint256", + }, + { internalType: "bytes32", name: "zoneHash", type: "bytes32" }, + { + internalType: "uint256", + name: "salt", + type: "uint256", + }, + { internalType: "bytes32", name: "offererConduitKey", type: "bytes32" }, + { + internalType: "bytes32", + name: "fulfillerConduitKey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "totalOriginalAdditionalRecipients", + type: "uint256", + }, + { + components: [ + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct AdditionalRecipient[]", + name: "additionalRecipients", + type: "tuple[]", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct BasicOrderParameters", + name: "parameters", + type: "tuple", + }, + ], + name: "fulfillBasicOrder_efficient_6GL6yc", + outputs: [{ internalType: "bool", name: "fulfilled", type: "bool" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "offerer", + type: "address", + }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + internalType: "struct OrderParameters", + name: "parameters", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct Order", + name: "", + type: "tuple", + }, + { internalType: "bytes32", name: "fulfillerConduitKey", type: "bytes32" }, + ], + name: "fulfillOrder", + outputs: [{ internalType: "bool", name: "fulfilled", type: "bool" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "contractOfferer", type: "address" }], + name: "getContractOffererNonce", + outputs: [{ internalType: "uint256", name: "nonce", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "offerer", type: "address" }], + name: "getCounter", + outputs: [{ internalType: "uint256", name: "counter", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "offerer", type: "address" }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "counter", type: "uint256" }, + ], + internalType: "struct OrderComponents", + name: "", + type: "tuple", + }, + ], + name: "getOrderHash", + outputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "orderHash", type: "bytes32" }], + name: "getOrderStatus", + outputs: [ + { internalType: "bool", name: "isValidated", type: "bool" }, + { + internalType: "bool", + name: "isCancelled", + type: "bool", + }, + { internalType: "uint256", name: "totalFilled", type: "uint256" }, + { + internalType: "uint256", + name: "totalSize", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "incrementCounter", + outputs: [{ internalType: "uint256", name: "newCounter", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "information", + outputs: [ + { internalType: "string", name: "version", type: "string" }, + { + internalType: "bytes32", + name: "domainSeparator", + type: "bytes32", + }, + { internalType: "address", name: "conduitController", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "offerer", + type: "address", + }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + internalType: "struct OrderParameters", + name: "parameters", + type: "tuple", + }, + { internalType: "uint120", name: "numerator", type: "uint120" }, + { + internalType: "uint120", + name: "denominator", + type: "uint120", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + { + internalType: "bytes", + name: "extraData", + type: "bytes", + }, + ], + internalType: "struct AdvancedOrder[]", + name: "", + type: "tuple[]", + }, + { + components: [ + { + internalType: "uint256", + name: "orderIndex", + type: "uint256", + }, + { internalType: "enum Side", name: "side", type: "uint8" }, + { + internalType: "uint256", + name: "index", + type: "uint256", + }, + { internalType: "uint256", name: "identifier", type: "uint256" }, + { + internalType: "bytes32[]", + name: "criteriaProof", + type: "bytes32[]", + }, + ], + internalType: "struct CriteriaResolver[]", + name: "", + type: "tuple[]", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "orderIndex", + type: "uint256", + }, + { internalType: "uint256", name: "itemIndex", type: "uint256" }, + ], + internalType: "struct FulfillmentComponent[]", + name: "offerComponents", + type: "tuple[]", + }, + { + components: [ + { + internalType: "uint256", + name: "orderIndex", + type: "uint256", + }, + { internalType: "uint256", name: "itemIndex", type: "uint256" }, + ], + internalType: "struct FulfillmentComponent[]", + name: "considerationComponents", + type: "tuple[]", + }, + ], + internalType: "struct Fulfillment[]", + name: "", + type: "tuple[]", + }, + { internalType: "address", name: "recipient", type: "address" }, + ], + name: "matchAdvancedOrders", + outputs: [ + { + components: [ + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifier", + type: "uint256", + }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { + internalType: "address payable", + name: "recipient", + type: "address", + }, + ], + internalType: "struct ReceivedItem", + name: "item", + type: "tuple", + }, + { internalType: "address", name: "offerer", type: "address" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + ], + internalType: "struct Execution[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "offerer", + type: "address", + }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + internalType: "struct OrderParameters", + name: "parameters", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct Order[]", + name: "", + type: "tuple[]", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "orderIndex", + type: "uint256", + }, + { internalType: "uint256", name: "itemIndex", type: "uint256" }, + ], + internalType: "struct FulfillmentComponent[]", + name: "offerComponents", + type: "tuple[]", + }, + { + components: [ + { + internalType: "uint256", + name: "orderIndex", + type: "uint256", + }, + { internalType: "uint256", name: "itemIndex", type: "uint256" }, + ], + internalType: "struct FulfillmentComponent[]", + name: "considerationComponents", + type: "tuple[]", + }, + ], + internalType: "struct Fulfillment[]", + name: "", + type: "tuple[]", + }, + ], + name: "matchOrders", + outputs: [ + { + components: [ + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifier", + type: "uint256", + }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { + internalType: "address payable", + name: "recipient", + type: "address", + }, + ], + internalType: "struct ReceivedItem", + name: "item", + type: "tuple", + }, + { internalType: "address", name: "offerer", type: "address" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + ], + internalType: "struct Execution[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "offerer", + type: "address", + }, + { + internalType: "address", + name: "zone", + type: "address", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + ], + internalType: "struct OfferItem[]", + name: "offer", + type: "tuple[]", + }, + { + components: [ + { + internalType: "enum ItemType", + name: "itemType", + type: "uint8", + }, + { internalType: "address", name: "token", type: "address" }, + { + internalType: "uint256", + name: "identifierOrCriteria", + type: "uint256", + }, + { internalType: "uint256", name: "startAmount", type: "uint256" }, + { + internalType: "uint256", + name: "endAmount", + type: "uint256", + }, + { internalType: "address payable", name: "recipient", type: "address" }, + ], + internalType: "struct ConsiderationItem[]", + name: "consideration", + type: "tuple[]", + }, + { internalType: "enum OrderType", name: "orderType", type: "uint8" }, + { + internalType: "uint256", + name: "startTime", + type: "uint256", + }, + { internalType: "uint256", name: "endTime", type: "uint256" }, + { + internalType: "bytes32", + name: "zoneHash", + type: "bytes32", + }, + { internalType: "uint256", name: "salt", type: "uint256" }, + { + internalType: "bytes32", + name: "conduitKey", + type: "bytes32", + }, + { internalType: "uint256", name: "totalOriginalConsiderationItems", type: "uint256" }, + ], + internalType: "struct OrderParameters", + name: "parameters", + type: "tuple", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct Order[]", + name: "", + type: "tuple[]", + }, + ], + name: "validate", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +] as const; diff --git a/packages/datasources/src/lib/types.ts b/packages/datasources/src/lib/types.ts index f6a415ee1..56f239ec7 100644 --- a/packages/datasources/src/lib/types.ts +++ b/packages/datasources/src/lib/types.ts @@ -55,6 +55,7 @@ export const DatasourceNames = { ENSRoot: "ensroot", Basenames: "basenames", Lineanames: "lineanames", + Seaport: "seaport", ThreeDNSOptimism: "threedns-optimism", ThreeDNSBase: "threedns-base", ReverseResolverRoot: "reverse-resolver-root", diff --git a/packages/datasources/src/mainnet.ts b/packages/datasources/src/mainnet.ts index b95c37f8a..fe60d7088 100644 --- a/packages/datasources/src/mainnet.ts +++ b/packages/datasources/src/mainnet.ts @@ -25,6 +25,7 @@ import { NameWrapper as linea_NameWrapper } from "./abis/lineanames/NameWrapper" import { Registry as linea_Registry } from "./abis/lineanames/Registry"; import { ThreeDNSToken } from "./abis/threedns/ThreeDNSToken"; +import { Seaport } from "./abis/seaport/Seaport"; // Shared ABIs import { StandaloneReverseRegistrar } from "./abis/shared/StandaloneReverseRegistrar"; import { ResolverABI, ResolverFilter } from "./lib/resolver"; @@ -426,4 +427,15 @@ export default { }, }, }, + + [DatasourceNames.Seaport]: { + chain: mainnet, + contracts: { + Seaport: { + abi: Seaport, + address: "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC", + startBlock: 17129405, + }, + }, + }, } satisfies ENSNamespace; diff --git a/packages/datasources/src/sepolia.ts b/packages/datasources/src/sepolia.ts index 33ca2651c..41dfa1577 100644 --- a/packages/datasources/src/sepolia.ts +++ b/packages/datasources/src/sepolia.ts @@ -30,6 +30,7 @@ import { EthRegistrarController as linea_EthRegistrarController } from "./abis/l import { NameWrapper as linea_NameWrapper } from "./abis/lineanames/NameWrapper"; import { Registry as linea_Registry } from "./abis/lineanames/Registry"; +import { Seaport } from "./abis/seaport/Seaport"; // Shared ABIs import { StandaloneReverseRegistrar } from "./abis/shared/StandaloneReverseRegistrar"; import { ResolverABI, ResolverFilter } from "./lib/resolver"; @@ -351,4 +352,15 @@ export default { }, }, }, + + [DatasourceNames.Seaport]: { + chain: sepolia, + contracts: { + Seaport: { + abi: Seaport, + address: "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC", + startBlock: 3365529, + }, + }, + }, } satisfies ENSNamespace; diff --git a/packages/ensnode-schema/src/ponder.schema.ts b/packages/ensnode-schema/src/ponder.schema.ts index f2a58f971..0c132ab22 100644 --- a/packages/ensnode-schema/src/ponder.schema.ts +++ b/packages/ensnode-schema/src/ponder.schema.ts @@ -3,6 +3,7 @@ */ export * from "./subgraph.schema"; export * from "./resolver-records.schema"; +export * from "./tokenscope.schema"; export * from "./resolver-relations.schema"; export * from "./referrals.schema"; export * from "./primary-names.schema"; diff --git a/packages/ensnode-schema/src/tokenscope.schema.ts b/packages/ensnode-schema/src/tokenscope.schema.ts new file mode 100644 index 000000000..97f2fb5fb --- /dev/null +++ b/packages/ensnode-schema/src/tokenscope.schema.ts @@ -0,0 +1,136 @@ +import schema from "ponder:schema"; +import { index, onchainTable } from "ponder"; + +export const TokenTypes = { + ERC721: "ERC721", + ERC1155: "ERC1155", +} as const; + +export type TokenType = (typeof TokenTypes)[keyof typeof TokenTypes]; + +const sharedEventColumns = (t: any) => ({ + /** + * The unique identifier of the event. + * + * Composite key format: "{chainId}-{blockNumber}-{logIndex}" (e.g., "1-1234567-5") + */ + id: t.text().primaryKey(), + + /** + * The block number where this event was emitted. + */ + blockNumber: t.integer().notNull(), + + /** + * The log index position of this event within its containing block. + * Determines event ordering when multiple events occur in the same block. + */ + logIndex: t.integer().notNull(), + + /** + * The transaction hash that generated this event. + */ + transactionID: t.hex().notNull(), + + /** + * The blockchain network identifier where this event occurred. + */ + chainId: t.integer().notNull(), +}); + +export const nameSold = onchainTable( + "name_sold", + (t) => ({ + ...sharedEventColumns(t), + + /** + * The account that previously owned and sold the domain. + * + * Must have been the verified owner of the domain (domainId) and NFT (contractAddress + tokenId) + * before this sale. Received the payment specified by 'price' + 'currency' from newOwnerId. + */ + fromOwnerId: t.hex().notNull(), + + /** + * The account that purchased and now owns the domain. + * + * Became the new owner of the domain (domainId) and its NFT representation (contractAddress + tokenId) + * after paying the amount specified by 'price' + 'currency' to fromOwnerId. + */ + newOwnerId: t.hex().notNull(), + + /** + * Currency address of the payment (ETH, USDC, WETH, or DAI). + * + * Works in conjunction with 'price' field to define the complete payment amount. + * Currency contract address varies by chainId - same token has different addresses on different chains. + */ + currency: t.text().notNull(), + + /** + * The payment amount denominated in the smallest unit of the currency specified in 'currency' field. + * + * Amount interpretation depends on currency: + * - ETH/WETH: Amount in wei (1 ETH = 10^18 wei) + * - USDC: Amount in micro-units (1 USDC = 10^6 units) + * - DAI: Amount in wei-equivalent (1 DAI = 10^18 units) + * + * This value MUST be read together with 'currency' to determine actual payment value. + */ + price: t.bigint().notNull(), + + /** + * Seaport protocol order hash identifying this specific sale order. + * Generated from order components (considerations, offers, etc.) to create unique order reference. + */ + orderHash: t.hex().notNull(), + + /** + * The ID of the token being sold within the contractAddress. + * + * Combined with 'contractAddress', creates unique NFT identifier for the domain being transferred. + * Interpretation depends on 'tokenType': + * - ERC721: Unique token within contract + * - ERC1155: Token type identifier (multiple copies may exist) + */ + tokenId: t.text().notNull(), + + /** + * The contract address of the token being sold. + * + * Works with 'tokenId' to uniquely identify the NFT representing the domain. + * Contract type must match 'tokenType' field (ERC721 or ERC1155). + * Address validity depends on 'chainId' where transaction occurred. + */ + contractAddress: t.hex().notNull(), + + /** + * The namehash of the ENS domain being sold. + * + * Links this sale to the specific ENS domain. + */ + domainId: t.hex().notNull(), + + /** + * Unix timestamp of when the domain sale occurred (block timestamp). + * + * Corresponds to the timestamp of the block identified by 'blockNumber' + 'chainId'. + */ + timestamp: t.bigint().notNull(), + + /** + * The type of token being sold (ERC721 or ERC1155). + */ + tokenType: t.text().notNull().$type(), + }), + (t) => ({ + idx_from: index().on(t.fromOwnerId), + idx_to: index().on(t.newOwnerId), + idx_domain: index().on(t.domainId), + idx_compound: index().on(t.fromOwnerId, t.id), + idx_created: index().on(t.timestamp), + }), +); + +export type NameSoldInsert = typeof schema.nameSold.$inferInsert; +export type NameSold = typeof schema.nameSold.$inferSelect; diff --git a/packages/ensnode-sdk/src/ens/constants.ts b/packages/ensnode-sdk/src/ens/constants.ts index 18736b3ed..be53fd860 100644 --- a/packages/ensnode-sdk/src/ens/constants.ts +++ b/packages/ensnode-sdk/src/ens/constants.ts @@ -3,6 +3,8 @@ import { namehash } from "viem"; import type { Node } from "./types"; export const ROOT_NODE: Node = namehash(""); +export const ETH_NODE = namehash("eth"); +export const BASE_NODE = namehash("base.eth"); /** * A set of nodes whose children are used for reverse resolution. diff --git a/packages/ensnode-sdk/src/ensindexer/config/types.ts b/packages/ensnode-sdk/src/ensindexer/config/types.ts index 0118bac7e..7f84cde21 100644 --- a/packages/ensnode-sdk/src/ensindexer/config/types.ts +++ b/packages/ensnode-sdk/src/ensindexer/config/types.ts @@ -12,6 +12,7 @@ export enum PluginName { ThreeDNS = "threedns", ReverseResolvers = "reverse-resolvers", Referrals = "referrals", + TokenScope = "tokenscope", } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa6e3e019..35da1405a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -288,6 +288,9 @@ importers: '@hono/zod-validator': specifier: ^0.7.2 version: 0.7.2(hono@4.7.8)(zod@3.25.7) + '@opensea/seaport-js': + specifier: ^4.0.5 + version: 4.0.5(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.0(patch_hash=4b2adeefaf7c22f9987d0a125d69cab900719bec7ed7636648bea6947107033a) @@ -569,6 +572,9 @@ importers: packages/datasources: dependencies: + '@ensnode/ensnode-sdk': + specifier: workspace:* + version: link:../ensnode-sdk '@ponder/utils': specifier: 'catalog:' version: 0.2.10(typescript@5.7.3)(viem@2.23.2(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)(zod@3.25.7)) @@ -1970,6 +1976,9 @@ packages: cpu: [x64] os: [win32] + '@noble/curves@1.2.0': + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + '@noble/curves@1.4.0': resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==} @@ -1980,6 +1989,10 @@ packages: resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.3.2': + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} @@ -2060,6 +2073,10 @@ packages: '@octokit/types@13.10.0': resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + '@opensea/seaport-js@4.0.5': + resolution: {integrity: sha512-hyJEHSCFmO7kv2G+ima0kCpt0kvLa6QOSHb1HJuLd8DS3bao0gOa/Q3AhM3xUqO6SZZ8aD9njhu1EDqjC/5pOw==} + engines: {node: '>=20.0.0'} + '@opentelemetry/api-logs@0.202.0': resolution: {integrity: sha512-fTBjMqKCfotFWfLzaKyhjLvyEyq5vDKTTFfBmx21btv3gvy8Lq6N5Dh2OzqeuN4DjtpSvNT1uNVfg08eD2Rfxw==} engines: {node: '>=8.0.0'} @@ -3473,6 +3490,9 @@ packages: '@types/node@22.15.3': resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==} + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/progress@2.0.7': resolution: {integrity: sha512-iadjw02vte8qWx7U0YM++EybBha2CQLPGu9iJ97whVgJUT5Zq9MjAPYUnbfRI2Kpehimf1QjFJYxD0t8nqzu5w==} @@ -3736,6 +3756,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + aes-js@4.0.0-beta.5: + resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -4000,6 +4023,9 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-reverse@1.0.1: + resolution: {integrity: sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==} + buffer-writer@2.0.0: resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} engines: {node: '>=4'} @@ -4285,6 +4311,9 @@ packages: crossws@0.3.3: resolution: {integrity: sha512-/71DJT3xJlqSnBr83uGJesmVHSzZEvgxHt/fIKxBAAngqMHmnBWQNxCphVxxJ2XL3xleu5+hJD6IQ3TglBedcw==} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} @@ -5056,6 +5085,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + ethers@6.15.0: + resolution: {integrity: sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==} + engines: {node: '>=14.0.0'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -6211,6 +6244,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + merkletreejs@0.5.2: + resolution: {integrity: sha512-MHqclSWRSQQbYciUMALC3PZmE23NPf5IIYo+Z7qAz5jVcqgCB95L1T9jGcr+FtOj2Pa2/X26uG2Xzxs7FJccUg==} + engines: {node: '>= 7.6.0'} + mermaid@11.10.0: resolution: {integrity: sha512-oQsFzPBy9xlpnGxUqLbVY8pvknLlsNIJ0NWwi8SUJjhbP1IT0E0o1lfhU4iYV3ubpy+xkzkaOyDUQMn06vQElQ==} @@ -7721,6 +7758,10 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + treeify@1.1.0: + resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==} + engines: {node: '>=0.6'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -7773,6 +7814,9 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -9988,6 +10032,10 @@ snapshots: '@next/swc-win32-x64-msvc@15.2.4': optional: true + '@noble/curves@1.2.0': + dependencies: + '@noble/hashes': 1.3.2 + '@noble/curves@1.4.0': dependencies: '@noble/hashes': 1.4.0 @@ -10000,6 +10048,8 @@ snapshots: dependencies: '@noble/hashes': 1.7.1 + '@noble/hashes@1.3.2': {} + '@noble/hashes@1.4.0': {} '@noble/hashes@1.5.0': {} @@ -10083,6 +10133,14 @@ snapshots: dependencies: '@octokit/openapi-types': 24.2.0 + '@opensea/seaport-js@4.0.5(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + ethers: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + merkletreejs: 0.5.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@opentelemetry/api-logs@0.202.0': dependencies: '@opentelemetry/api': 1.9.0(patch_hash=4b2adeefaf7c22f9987d0a125d69cab900719bec7ed7636648bea6947107033a) @@ -11503,6 +11561,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.7.5': + dependencies: + undici-types: 6.19.8 + '@types/progress@2.0.7': dependencies: '@types/node': 22.15.3 @@ -11877,6 +11939,8 @@ snapshots: acorn@8.15.0: {} + aes-js@4.0.0-beta.5: {} + agent-base@7.1.4: {} agentkeepalive@4.6.0: @@ -12265,6 +12329,8 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-reverse@1.0.1: {} + buffer-writer@2.0.0: {} buffer@6.0.3: @@ -12557,6 +12623,8 @@ snapshots: dependencies: uncrypto: 0.1.3 + crypto-js@4.2.0: {} + css-select@5.1.0: dependencies: boolbase: 1.0.0 @@ -13405,6 +13473,19 @@ snapshots: esutils@2.0.3: {} + ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): + dependencies: + '@adraffy/ens-normalize': 1.10.1 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 22.7.5 + aes-js: 4.0.0-beta.5 + tslib: 2.7.0 + ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + event-target-shim@5.0.1: {} eventemitter3@5.0.1: {} @@ -14825,6 +14906,12 @@ snapshots: merge2@1.4.1: {} + merkletreejs@0.5.2: + dependencies: + buffer-reverse: 1.0.1 + crypto-js: 4.2.0 + treeify: 1.1.0 + mermaid@11.10.0: dependencies: '@braintree/sanitize-url': 7.1.1 @@ -16794,6 +16881,8 @@ snapshots: tree-kill@1.2.2: {} + treeify@1.1.0: {} + trim-lines@3.0.1: {} trim-trailing-lines@2.1.0: {} @@ -16840,6 +16929,8 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@2.7.0: {} + tslib@2.8.1: {} tsup@8.3.6(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.7.3)(yaml@2.7.0):