Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
58e6ab0
added seaport orderfulfill event
Zimtente Jul 14, 2025
61bfcf2
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente Jul 14, 2025
eb24a13
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente Jul 25, 2025
9d6fae4
Merge branch 'main' into 832-seaport-sales-plugin
tk-o Jul 28, 2025
f8433f1
docs(changeset): Added new Plugin: TokenScope. This Plugin for now wi…
Zimtente Aug 4, 2025
751754f
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente Aug 4, 2025
0c4485b
working on review items
Zimtente Aug 11, 2025
cfdc375
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente Aug 11, 2025
b562d5f
Merge branch 'main' into 832-seaport-sales-plugin
Zimtente Aug 24, 2025
a78c927
last changes
Zimtente Aug 25, 2025
956d9a2
remove unnecessary context param
lightwalker-eth Aug 25, 2025
1dc40ea
lint
lightwalker-eth Aug 25, 2025
115f41f
Rename function
lightwalker-eth Aug 25, 2025
9cbf536
Fix chainId type
lightwalker-eth Aug 25, 2025
fa074d7
Move TokenType definition
lightwalker-eth Aug 25, 2025
783b9c1
Refine isEqualChainAddress
lightwalker-eth Aug 25, 2025
5a0643a
Refactor getDomainIdByTokenId and related
lightwalker-eth Aug 25, 2025
5756db6
Refactor isSupportedCurrencyContract
lightwalker-eth Aug 25, 2025
d8aa7d8
Refine currency logic
lightwalker-eth Aug 25, 2025
9c90320
Refine indexing logic
lightwalker-eth Aug 25, 2025
3d12dde
Remove unnecessary .gitattributes
lightwalker-eth Aug 25, 2025
6507c95
Make indexing of Seaport1.5 explicit
lightwalker-eth Aug 25, 2025
bcc38ae
Remove circular package dependency
lightwalker-eth Aug 25, 2025
f8e50dc
Refine docs
lightwalker-eth Aug 25, 2025
5ece4f6
Refine docs
lightwalker-eth Aug 25, 2025
6631cd8
Refine data model
lightwalker-eth Aug 25, 2025
eb07501
Refactorings
lightwalker-eth Aug 25, 2025
5e1e97e
Refactorings
lightwalker-eth Aug 25, 2025
694f225
typecheck
lightwalker-eth Aug 25, 2025
1b1f2d1
Update changeset
lightwalker-eth Aug 25, 2025
6841aa8
lint
lightwalker-eth Aug 25, 2025
da5787c
Update changeset
lightwalker-eth Aug 25, 2025
cc8c909
Revert changes to shell script executable permissions
lightwalker-eth Aug 25, 2025
cd53bf7
Advertise new tokenscope plugin
lightwalker-eth Aug 25, 2025
4e3b81d
Revert chainId import changes
lightwalker-eth Aug 25, 2025
ce7d125
Document division of responsibilities
lightwalker-eth Aug 25, 2025
2835c35
Add docs
lightwalker-eth Aug 25, 2025
aa29450
Refine docs
lightwalker-eth Aug 25, 2025
b8761f1
lint
lightwalker-eth Aug 25, 2025
5bfc851
Merge branch 'main' into feat/tokenscope
lightwalker-eth Aug 25, 2025
b8d065a
add tokenscope plugin to alpha docs list
shrugs Aug 25, 2025
c7da331
refactor: move handler and lib logics to correct place
shrugs Aug 25, 2025
6ef1dea
fix: streamline some conditionals
shrugs Aug 25, 2025
469d36d
refactor: move stuff out of datasources, fix handler placement, refac…
shrugs Aug 25, 2025
084ad4d
fix: remove console log
shrugs Aug 25, 2025
13e05f3
fix: type referential error
shrugs Aug 25, 2025
0626cb6
remove redundant satisfies operators
shrugs Aug 25, 2025
99ede8f
Merge branch 'main' into feat/tokenscope
shrugs Aug 25, 2025
66d6a4c
Refine docs
lightwalker-eth Aug 26, 2025
6fb84b2
Refine docs on no-op case
lightwalker-eth Aug 26, 2025
428b36d
Fix data model
lightwalker-eth Aug 26, 2025
fa9e488
Add validation that supported NFTs are exactly 1
lightwalker-eth Aug 26, 2025
83cb1ed
Align language
lightwalker-eth Aug 26, 2025
0632022
Refine language
lightwalker-eth Aug 26, 2025
02e6fce
Refactor currency
lightwalker-eth Aug 26, 2025
0d150b1
Refine docs
lightwalker-eth Aug 26, 2025
4cf08d3
refine docs
lightwalker-eth Aug 26, 2025
461c807
Constrain exports
lightwalker-eth Aug 26, 2025
d15e9af
Remove unused function
lightwalker-eth Aug 26, 2025
d02e9cf
lint
lightwalker-eth Aug 26, 2025
73d04db
constrain exports
lightwalker-eth Aug 26, 2025
4f6d029
Refine docs
lightwalker-eth Aug 26, 2025
7c26c8d
Refine docs on hosted instances
lightwalker-eth Aug 26, 2025
48689a4
fix: remove Seaport* prefix for seaport-types
shrugs Aug 26, 2025
8eed36b
fix: remove unused function
shrugs Aug 26, 2025
4329fda
fix: clean up currency handling
shrugs Aug 26, 2025
abfc68b
fix: boolean error in offer/consideration validity check
shrugs Aug 26, 2025
e196b4e
Merge branch 'main' into feat/tokenscope
shrugs Aug 26, 2025
3dfcc2c
fix: update docstring
shrugs Aug 26, 2025
522619f
fix: do not enable tokenscope just yet
shrugs Aug 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/six-pillows-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@ensnode/ensnode-schema": minor
"@ensnode/datasources": minor
"ensindexer": minor
---

Initial launch of ENS TokenScope with support for indexing Seaport sales.
2 changes: 1 addition & 1 deletion .github/workflows/test_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
# We use private RPC URLs from GitHub Secrets to avoid rate limits.
# Public RPC URLs are used as fallbacks for repository forks
# that don't have the relevant secrets configured.
PLUGINS: subgraph,basenames,lineanames,threedns,reverse-resolvers,referrals
PLUGINS: subgraph,basenames,lineanames,threedns,reverse-resolvers,referrals,tokenscope
RPC_URL_1: ${{ secrets.MAINNET_RPC_URL || 'https://eth.drpc.org' }}
RPC_URL_10: ${{ secrets.OPTIMISM_RPC_URL || 'https://optimism.drpc.org' }}
RPC_URL_8453: ${{ secrets.BASE_RPC_URL || 'https://base.drpc.org' }}
Expand Down
2 changes: 1 addition & 1 deletion apps/ensadmin/src/lib/chains.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Datasource, type ENSNamespaceId, getENSNamespace } from "@ensnode/datasources";
import { type Datasource, type ENSNamespaceId, getENSNamespace } from "@ensnode/datasources";
import { type Chain } from "viem";
import { anvil } from "viem/chains";

Expand Down
5 changes: 4 additions & 1 deletion apps/ensindexer/.env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ PORT=42069
# === ENS Namespace: Mainnet ===
# Ethereum Mainnet
# - required if the configured namespace is mainnet
# - required by plugins: subgraph, reverse-resolvers, referrals, tokenscope
RPC_URL_1=
RPC_REQUEST_RATE_LIMIT_1=500

Expand Down Expand Up @@ -71,6 +72,7 @@ RPC_REQUEST_RATE_LIMIT_534352=500
# === ENS Namespace: Sepolia ===
# Ethereum Sepolia (public testnet)
# - required if the configured namespace is sepolia
# - required by plugins: subgraph, reverse-resolvers, referrals, tokenscope
RPC_URL_11155111=
RPC_REQUEST_RATE_LIMIT_11155111=500

Expand Down Expand Up @@ -102,6 +104,7 @@ RPC_REQUEST_RATE_LIMIT_534351=500
# === ENS Namespace: Holesky ===
# Ethereum Holesky (public testnet)
# - required if the configured namespace is holesky
# - required by plugins: subgraph, reverse-resolvers, referrals
RPC_URL_17000=
RPC_REQUEST_RATE_LIMIT_17000=500

Expand Down Expand Up @@ -145,7 +148,7 @@ NAMESPACE=mainnet
# - for subgraph-compatible indexing, the only valid configuration is `PLUGINS=subgraph`.
# - for protocol acceleration of primary name lookups (reverse resolution), enable the
# reverse-resolvers plugin.
PLUGINS=subgraph,basenames,lineanames,threedns,reverse-resolvers,referrals
PLUGINS=subgraph,basenames,lineanames,threedns,reverse-resolvers,referrals,tokenscope

# ENSRainbow service URL
# Required. This is the URL of the ENSRainbow server that ENSIndexer will use to heal
Expand Down
1 change: 1 addition & 0 deletions apps/ensindexer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@opentelemetry/sdk-trace-node": "^2.0.1",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@types/dns-packet": "^5.6.5",
"caip": "^1.1.1",
"date-fns": "catalog:",
"deepmerge-ts": "^7.1.5",
"dns-packet": "^5.6.1",
Expand Down
149 changes: 149 additions & 0 deletions apps/ensindexer/src/lib/currencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
base,
baseSepolia,
holesky,
linea,
lineaSepolia,
mainnet,
optimism,
sepolia,
} from "viem/chains";

import { AccountId, ChainId, accountIdEqual } from "@ensnode/ensnode-sdk";
import { Address, zeroAddress } from "viem";

/**
* Identifiers for supported currencies.
*
* TODO: Add support for WETH
*/
export const CurrencyIds = {
ETH: "ETH",
USDC: "USDC",
DAI: "DAI",
} as const;

export type CurrencyId = (typeof CurrencyIds)[keyof typeof CurrencyIds];

export interface Price {
currency: CurrencyId;

/**
* The amount of the currency in the smallest unit of the currency. (see
* decimals of the CurrencyConfig for the currency).
*
* Guaranteed to be non-negative.
*/
amount: bigint;
}

export interface CurrencyInfo {
id: CurrencyId;
name: string;
decimals: number;
}

const currencyInfo: Record<CurrencyId, CurrencyInfo> = {
[CurrencyIds.ETH]: {
id: CurrencyIds.ETH,
name: "Ethereum",
decimals: 18,
},
[CurrencyIds.USDC]: {
id: CurrencyIds.USDC,
name: "USDC",
decimals: 6,
},
[CurrencyIds.DAI]: {
id: CurrencyIds.DAI,
name: "Dai Stablecoin",
decimals: 18,
},
};

export const getCurrencyInfo = (currencyId: CurrencyId): CurrencyInfo => currencyInfo[currencyId];

// NOTE: this mapping currently only considers the subset of chains where we have
// supported token issuing contracts.
const KNOWN_CURRENCY_CONTRACTS: Record<ChainId, Record<CurrencyId, Address>> = {
/** mainnet namespace */
[mainnet.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0xA0b86a33E6417c5Dd4Baf8C54e5de49E293E9169",
[CurrencyIds.DAI]: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
},
[base.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
[CurrencyIds.DAI]: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
},
[optimism.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
[CurrencyIds.DAI]: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
},
[linea.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff",
[CurrencyIds.DAI]: "0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5",
},

/** sepolia namespace */
[sepolia.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
[CurrencyIds.DAI]: "0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6",
},
[baseSepolia.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
[CurrencyIds.DAI]: "0x7368C6C68a4b2b68F90DB2e8F5E3b8E1E5e4F5c7",
},
[lineaSepolia.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff",
[CurrencyIds.DAI]: "0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5",
},

/** holesky namespace */
[holesky.id]: {
[CurrencyIds.ETH]: zeroAddress,
[CurrencyIds.USDC]: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
[CurrencyIds.DAI]: "0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6",
},
} as const;

/**
* Gets the supported currency contracts for a given chain as a Record<CurrencyId, AccountId>
*
* @param chainId - The chain ID to get supported currency contracts for
* @returns a record of currency ids to AccountIds for the given chain
*/
const getSupportedCurrencyContractsForChain = (chainId: ChainId): Record<CurrencyId, AccountId> => {
return Object.fromEntries(
Object.entries(KNOWN_CURRENCY_CONTRACTS[chainId] ?? {}).map(([currencyId, address]) => [
currencyId,
{ chainId, address },
]),
) as Record<CurrencyId, AccountId>;
};

/**
* Gets the currency id for the given contract
*
* @param contract - The AccountId of the contract to get the currency id for
* @returns the currency id for the given contract in the specified namespace, or
* null if the contract is not a supported currency contract in the
* specified namespace
*/
export const getCurrencyIdForContract = (contract: AccountId): CurrencyId | null => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For strict correctness it seems we should:

  1. Keep a namespace param on getCurrencyIdForContract.
  2. Update the data model for KNOWN_CURRENCY_CONTRACTS so that each record includes its associated namespace.
  3. Update getSupportedCurrencyContractsForChain so that it also takes a namespace param.
    1. Update logic in this function so that it filters by namespace in addition to the existing chain filtering logic.

const supportedCurrencyContracts = getSupportedCurrencyContractsForChain(contract.chainId);

const found = Object.entries(supportedCurrencyContracts).find(([, accountId]) =>
accountIdEqual(accountId, contract),
);

if (!found) return null;

return found[0] as CurrencyId;
};
38 changes: 38 additions & 0 deletions apps/ensindexer/src/lib/datasource-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
DatasourceName,
ENSNamespaceId,
getENSNamespace,
maybeGetDatasource,
} from "@ensnode/datasources";
import { AccountId, ChainId, uniq } from "@ensnode/ensnode-sdk";

/**
* Gets the AccountId for the contract in the specified namespace, datasource, and
* contract name, or undefined if it is not defined or is not a single AccountId.
*
* This is useful when you want to retrieve the AccountId for a contract by its name
* where it may or may not actually be defined for the given namespace and datasource.
*
* @param namespaceId - The ENSNamespace identifier (e.g. 'mainnet', 'sepolia', 'holesky',
* 'ens-test-env')
* @param datasourceName - The name of the Datasource to search for contractName in
* @param contractName - The name of the contract to retrieve
* @returns The AccountId of the contract with the given namespace, datasource,
* and contract name, or undefined if it is not found or is not a single AccountId
*/
export const maybeGetDatasourceContract = (
namespaceId: ENSNamespaceId,
datasourceName: DatasourceName,
contractName: string,
): AccountId | undefined => {
const datasource = maybeGetDatasource(namespaceId, datasourceName);
if (!datasource) return undefined;

const address = datasource.contracts[contractName]?.address;
if (address === undefined || Array.isArray(address)) return undefined;

return {
chainId: datasource.chain.id,
address,
};
};
1 change: 0 additions & 1 deletion apps/ensindexer/src/lib/ponder-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Address, PublicClient } from "viem";
import * as z from "zod/v4";

import { ContractConfig } from "@ensnode/datasources";
import { EnsRainbowApiClient } from "@ensnode/ensrainbow-sdk";
import type { BlockInfo, PonderStatus } from "@ensnode/ponder-metadata";

import { ENSIndexerConfig } from "@/config/types";
Expand Down
14 changes: 14 additions & 0 deletions apps/ensindexer/src/lib/tokenscope/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* An enum representing the possible CAIP-19 Asset Namespace values.
*/
export const AssetNamespaces = {
ERC721: "erc721",
ERC1155: "erc1155",
} as const;

export type AssetNamespace = (typeof AssetNamespaces)[keyof typeof AssetNamespaces];

/**
* A uint256 value that identifies a specific token within an NFT contract.
*/
export type TokenId = bigint;
24 changes: 24 additions & 0 deletions apps/ensindexer/src/lib/tokenscope/sales.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AccountId, Node } from "@ensnode/ensnode-sdk";
import { Address, Hex } from "viem";

import { Price } from "@/lib/currencies";
import { AssetNamespace, TokenId } from "@/lib/tokenscope/assets";

export interface SupportedNFT {
assetNamespace: AssetNamespace;
contract: AccountId;
tokenId: TokenId;
domainId: Node;
}

export interface SupportedPayment {
price: Price;
}

export interface SupportedSale {
orderHash: Hex;
nft: SupportedNFT;
payment: SupportedPayment;
seller: Address;
buyer: Address;
}
Loading