-
Notifications
You must be signed in to change notification settings - Fork 15
832-seaport-sales-plugin #872
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
58e6ab0
added seaport orderfulfill event
Zimtente 61bfcf2
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente eb24a13
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente 9d6fae4
Merge branch 'main' into 832-seaport-sales-plugin
tk-o f8433f1
docs(changeset): Added new Plugin: TokenScope. This Plugin for now wi…
Zimtente 751754f
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente 0c4485b
working on review items
Zimtente cfdc375
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente b562d5f
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente a78c927
last changes
Zimtente File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Auto detect text files and perform LF normalization | ||
| * text=auto |
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
Zimtente marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| interface SeaportOrderFulfilledEvent | ||
| extends EventWithArgs<{ | ||
| /** | ||
| * The unique hash identifier of the fulfilled order. | ||
| * Used to track and reference specific orders on-chain. | ||
| */ | ||
| orderHash: Hex; | ||
Zimtente marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * 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<void> { | ||
| // 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); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.