diff --git a/packages/address-book/README.md b/packages/address-book/README.md new file mode 100644 index 000000000..fc7fe9412 --- /dev/null +++ b/packages/address-book/README.md @@ -0,0 +1,85 @@ +# @graphprotocol/address-book + +Contract addresses for The Graph Protocol. This package provides JSON files containing contract addresses for different networks. + +## Features + +- Contract addresses for Horizon and Subgraph Service +- Network-specific deployment addresses +- Zero dependencies + +## Installation + +```bash +npm install @graphprotocol/address-book +# or +pnpm install @graphprotocol/address-book +``` + +## Usage + +### Import addresses directly + +```javascript +// CommonJS +const horizonAddresses = require('@graphprotocol/address-book/horizon/addresses.json') +const subgraphServiceAddresses = require('@graphprotocol/address-book/subgraph-service/addresses.json') + +// ES Modules +import horizonAddresses from '@graphprotocol/address-book/horizon/addresses.json' +import subgraphServiceAddresses from '@graphprotocol/address-book/subgraph-service/addresses.json' +``` + +### Address format + +The addresses are organized by chain ID and contract name: + +```json +{ + "1337": { + "Controller": { + "address": "0x...", + "proxy": "transparent", + "proxyAdmin": "0x...", + "implementation": "0x..." + } + } +} +``` + +## Development + +This package uses symlinks to stay in sync with the source address files. On first install, symlinks are automatically created. + +## npm Publishing + +This package uses a special workflow to ensure address files are included in the published package: + +### How It Works + +**Development**: The package uses symlinks to stay in sync with source address files: + +- `src/horizon/addresses.json` → symlink to `../../../horizon/addresses.json` +- `src/subgraph-service/addresses.json` → symlink to `../../../subgraph-service/addresses.json` + +**Publishing**: npm doesn't include symlinks in packages, so we automatically handle this: + +```bash +npm publish +``` + +**Automatic execution**: + +1. **`prepublishOnly`** - Copies actual files to replace symlinks +2. **npm pack & publish** - Includes real address files in published package +3. **`postpublish`** - Restores symlinks for development + +### Troubleshooting + +If publishing fails, the `postpublish` script may not run, leaving copied files instead of symlinks. To restore symlinks manually: + +```bash +pnpm restore-symlinks +``` + +All symlink management is handled automatically during successful publishes. diff --git a/packages/address-book/package.json b/packages/address-book/package.json new file mode 100644 index 000000000..601b14dd0 --- /dev/null +++ b/packages/address-book/package.json @@ -0,0 +1,36 @@ +{ + "name": "@graphprotocol/address-book", + "version": "0.1.0", + "publishConfig": { + "access": "public" + }, + "description": "Contract addresses for The Graph Protocol", + "author": "The Graph core devs", + "license": "GPL-2.0-or-later", + "exports": { + "./horizon/addresses.json": "./src/horizon/addresses.json", + "./subgraph-service/addresses.json": "./src/subgraph-service/addresses.json" + }, + "files": [ + "src/**/*.json", + "README.md" + ], + "scripts": { + "lint": "prettier -w --cache --log-level warn '**/*.json'", + "restore-symlinks": "node scripts/restore-symlinks.js", + "prepublishOnly": "node scripts/copy-addresses-for-publish.js", + "postpublish": "node scripts/restore-symlinks.js" + }, + "devDependencies": { + "prettier": "^3.0.0" + }, + "keywords": [ + "ethereum", + "smart-contracts", + "graph", + "graph-protocol", + "horizon", + "subgraph-service", + "address-book" + ] +} diff --git a/packages/address-book/prettier.config.cjs b/packages/address-book/prettier.config.cjs new file mode 100644 index 000000000..42c971aaf --- /dev/null +++ b/packages/address-book/prettier.config.cjs @@ -0,0 +1 @@ +module.exports = require('../../prettier.config.cjs') diff --git a/packages/address-book/scripts/copy-addresses-for-publish.js b/packages/address-book/scripts/copy-addresses-for-publish.js new file mode 100755 index 000000000..5fdfdc2c2 --- /dev/null +++ b/packages/address-book/scripts/copy-addresses-for-publish.js @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +/** + * Copy Addresses for Publishing + * + * This script copies the actual addresses.json files from horizon and subgraph-service + * packages to replace the symlinks before npm publish. + * + * Why we need this: + * - Development uses symlinks (committed to git) for convenience + * - npm publish doesn't include symlinks in the published package + * - We need actual files in the published package for consumers + * + * The postpublish script will restore the symlinks after publishing. + */ + +const fs = require('fs') +const path = require('path') + +const FILES_TO_COPY = [ + { + source: '../../../horizon/addresses.json', + target: 'src/horizon/addresses.json', + }, + { + source: '../../../subgraph-service/addresses.json', + target: 'src/subgraph-service/addresses.json', + }, +] + +function copyFileForPublish(source, target) { + const targetPath = path.resolve(__dirname, '..', target) + const sourcePath = path.resolve(path.dirname(targetPath), source) + + // Ensure source exists + if (!fs.existsSync(sourcePath)) { + console.error(`❌ Source file ${sourcePath} does not exist`) + process.exit(1) + } + + // Remove existing symlink + if (fs.existsSync(targetPath)) { + fs.unlinkSync(targetPath) + } + + // Copy actual file + try { + fs.copyFileSync(sourcePath, targetPath) + console.log(`✅ Copied for publish: ${target} <- ${source}`) + } catch (error) { + console.error(`❌ Failed to copy ${source} to ${target}:`, error.message) + process.exit(1) + } +} + +function main() { + console.log('📦 Copying address files for npm publish...') + + for (const { source, target } of FILES_TO_COPY) { + copyFileForPublish(source, target) + } + + console.log('✅ Address files copied for publish!') +} + +if (require.main === module) { + main() +} diff --git a/packages/address-book/scripts/restore-symlinks.js b/packages/address-book/scripts/restore-symlinks.js new file mode 100755 index 000000000..05e5ec6f9 --- /dev/null +++ b/packages/address-book/scripts/restore-symlinks.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +/** + * Restore Symlinks After Publishing + * + * This script restores the symlinks after npm publish completes. + * The prepublishOnly script replaces symlinks with actual files for publishing, + * and this script puts the symlinks back for development. + */ + +const fs = require('fs') +const path = require('path') + +const SYMLINKS_TO_RESTORE = [ + { + target: '../../../horizon/addresses.json', + link: 'src/horizon/addresses.json', + }, + { + target: '../../../subgraph-service/addresses.json', + link: 'src/subgraph-service/addresses.json', + }, +] + +function restoreSymlink(target, link) { + const linkPath = path.resolve(__dirname, '..', link) + + // Remove the copied file + if (fs.existsSync(linkPath)) { + fs.unlinkSync(linkPath) + } + + // Restore symlink + try { + fs.symlinkSync(target, linkPath) + console.log(`✅ Restored symlink: ${link} -> ${target}`) + } catch (error) { + console.error(`❌ Failed to restore symlink ${link}:`, error.message) + process.exit(1) + } +} + +function main() { + console.log('🔗 Restoring symlinks after publish...') + + for (const { target, link } of SYMLINKS_TO_RESTORE) { + restoreSymlink(target, link) + } + + console.log('✅ Symlinks restored!') +} + +if (require.main === module) { + main() +} diff --git a/packages/address-book/src/horizon/addresses.json b/packages/address-book/src/horizon/addresses.json new file mode 120000 index 000000000..dcec24e00 --- /dev/null +++ b/packages/address-book/src/horizon/addresses.json @@ -0,0 +1 @@ +../../../horizon/addresses.json \ No newline at end of file diff --git a/packages/address-book/src/subgraph-service/addresses.json b/packages/address-book/src/subgraph-service/addresses.json new file mode 120000 index 000000000..c4e2f4633 --- /dev/null +++ b/packages/address-book/src/subgraph-service/addresses.json @@ -0,0 +1 @@ +../../../subgraph-service/addresses.json \ No newline at end of file diff --git a/packages/horizon/package.json b/packages/horizon/package.json index ef790af39..a6f45d9bd 100644 --- a/packages/horizon/package.json +++ b/packages/horizon/package.json @@ -17,8 +17,7 @@ "files": [ "build/contracts/**/*", "typechain-types/**/*", - "README.md", - "addresses.json" + "README.md" ], "scripts": { "lint": "pnpm lint:ts; pnpm lint:sol; pnpm lint:natspec; pnpm lint:json", diff --git a/packages/subgraph-service/package.json b/packages/subgraph-service/package.json index defc3e836..fa0e01405 100644 --- a/packages/subgraph-service/package.json +++ b/packages/subgraph-service/package.json @@ -15,8 +15,7 @@ "files": [ "build/contracts/**/*", "typechain-types/**/*", - "README.md", - "addresses.json" + "README.md" ], "scripts": { "lint": "pnpm lint:ts; pnpm lint:sol; pnpm lint:natspec; pnpm lint:json", diff --git a/packages/subgraph-service/test/integration/after-transition-period/dispute-manager/indexing-disputes.test.ts b/packages/subgraph-service/test/integration/after-transition-period/dispute-manager/indexing-disputes.test.ts index 94f0a688d..313cd155f 100644 --- a/packages/subgraph-service/test/integration/after-transition-period/dispute-manager/indexing-disputes.test.ts +++ b/packages/subgraph-service/test/integration/after-transition-period/dispute-manager/indexing-disputes.test.ts @@ -72,7 +72,8 @@ describe('Indexing Disputes', () => { await graphToken.connect(fisherman).approve(disputeManager.target, disputeDeposit) // Create dispute - const tx = await disputeManager.connect(fisherman).createIndexingDispute(allocationId, poi) + const currentBlockNumber = await ethers.provider.getBlockNumber() + const tx = await disputeManager.connect(fisherman).createIndexingDispute(allocationId, poi, currentBlockNumber) const receipt = await tx.wait() // Get dispute ID from event @@ -97,7 +98,8 @@ describe('Indexing Disputes', () => { await graphToken.connect(fisherman).approve(disputeManager.target, disputeDeposit) // Create dispute - const tx = await disputeManager.connect(fisherman).createIndexingDispute(allocationId, poi) + const currentBlockNumber = await ethers.provider.getBlockNumber() + const tx = await disputeManager.connect(fisherman).createIndexingDispute(allocationId, poi, currentBlockNumber) const receipt = await tx.wait() // Get dispute ID from event @@ -138,7 +140,8 @@ describe('Indexing Disputes', () => { // Create dispute const poi = generatePOI() - const tx = await disputeManager.connect(fisherman).createIndexingDispute(allocationId, poi) + const currentBlockNumber = await ethers.provider.getBlockNumber() + const tx = await disputeManager.connect(fisherman).createIndexingDispute(allocationId, poi, currentBlockNumber) const receipt = await tx.wait() // Get dispute ID from event diff --git a/packages/toolshed/package.json b/packages/toolshed/package.json index 6b2aff982..f179dc55f 100644 --- a/packages/toolshed/package.json +++ b/packages/toolshed/package.json @@ -50,6 +50,7 @@ "horizon" ], "dependencies": { + "@graphprotocol/address-book": "workspace:^", "@graphprotocol/interfaces": "workspace:^", "@nomicfoundation/hardhat-ethers": "3.0.8", "debug": "^4.4.0", diff --git a/packages/toolshed/src/deployments/contract.ts b/packages/toolshed/src/deployments/contract.ts index cc4b4c2fc..0a72d9103 100644 --- a/packages/toolshed/src/deployments/contract.ts +++ b/packages/toolshed/src/deployments/contract.ts @@ -9,10 +9,9 @@ export type ContractList = Partial * Loads a contract from an address book * * @param name Name of the contract - * @param addressBook Address book to use + * @param address Contract address * @param signerOrProvider Signer or provider to use * @param enableTxLogging Enable transaction logging to console and output file. Defaults to false. - * @param optional If true, the contract is optional and will not throw if it cannot be loaded * @returns the loaded contract * * @throws Error if the contract could not be loaded diff --git a/packages/toolshed/src/deployments/horizon/actions.ts b/packages/toolshed/src/deployments/horizon/actions.ts index f406e598f..8fc9bd4df 100644 --- a/packages/toolshed/src/deployments/horizon/actions.ts +++ b/packages/toolshed/src/deployments/horizon/actions.ts @@ -1,6 +1,6 @@ import type { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' -import type { GraphHorizonContracts } from '.' +import type { GraphHorizonContracts } from './contracts' /** * It's important to use JSDoc in the return functions here for good developer experience as diff --git a/packages/toolshed/src/deployments/horizon/index.ts b/packages/toolshed/src/deployments/horizon/index.ts index 6023fe54c..22b6c955b 100644 --- a/packages/toolshed/src/deployments/horizon/index.ts +++ b/packages/toolshed/src/deployments/horizon/index.ts @@ -19,7 +19,8 @@ export function loadGraphHorizon(addressBookPath: string, chainId: number, provi } export function connectGraphHorizon(chainId: number, signerOrProvider: Signer | Provider, addressBookPath?: string) { - addressBookPath = addressBookPath ?? resolveAddressBook(require, '@graphprotocol/horizon', 'addresses.json') + addressBookPath = + addressBookPath ?? resolveAddressBook(require, '@graphprotocol/address-book', 'horizon/addresses.json') if (!addressBookPath) { throw new Error('Address book path not found') } diff --git a/packages/toolshed/src/deployments/subgraph-service/index.ts b/packages/toolshed/src/deployments/subgraph-service/index.ts index f61f14d76..3f8ac865e 100644 --- a/packages/toolshed/src/deployments/subgraph-service/index.ts +++ b/packages/toolshed/src/deployments/subgraph-service/index.ts @@ -19,7 +19,8 @@ export function loadSubgraphService(addressBookPath: string, chainId: number, pr } export function connectSubgraphService(chainId: number, signerOrProvider: Signer | Provider, addressBookPath?: string) { - addressBookPath = addressBookPath ?? resolveAddressBook(require, '@graphprotocol/subgraph-service', 'addresses.json') + addressBookPath = + addressBookPath ?? resolveAddressBook(require, '@graphprotocol/address-book', 'subgraph-service/addresses.json') if (!addressBookPath) { throw new Error('Address book path not found') } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca6e08187..626ca740e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,6 +110,12 @@ importers: specifier: ^1.7.0 version: 1.7.0 + packages/address-book: + devDependencies: + prettier: + specifier: ^3.5.3 + version: 3.6.2 + packages/contracts: devDependencies: '@arbitrum/sdk': @@ -1167,6 +1173,9 @@ importers: packages/toolshed: dependencies: + '@graphprotocol/address-book': + specifier: workspace:^ + version: link:../address-book '@graphprotocol/interfaces': specifier: workspace:^ version: link:../interfaces @@ -3700,6 +3709,7 @@ packages: '@smithy/middleware-endpoint@4.1.14': resolution: {integrity: sha512-+BGLpK5D93gCcSEceaaYhUD/+OCGXM1IDaq/jKUQ+ujB0PTWlWN85noodKw/IPFZhIKFCNEe19PGd/reUMeLSQ==} engines: {node: '>=18.0.0'} + deprecated: Please upgrade to @smithy/middleware-endpoint@4.1.15 or higher to fix a bug preventing the resolution of ENV and config file custom endpoints https://github.com/smithy-lang/smithy-typescript/issues/1645 '@smithy/middleware-retry@4.1.15': resolution: {integrity: sha512-iKYUJpiyTQ33U2KlOZeUb0GwtzWR3C0soYcKuCnTmJrvt6XwTPQZhMfsjJZNw7PpQ3TU4Ati1qLSrkSJxnnSMQ==} @@ -20237,7 +20247,7 @@ snapshots: eth-json-rpc-middleware@1.6.0: dependencies: - async: 2.6.2 + async: 2.6.4 eth-query: 2.1.2 eth-tx-summary: 3.2.4 ethereumjs-block: 1.7.1 @@ -20295,7 +20305,7 @@ snapshots: eth-tx-summary@3.2.4: dependencies: - async: 2.6.2 + async: 2.6.4 clone: 2.1.2 concat-stream: 1.6.2 end-of-stream: 1.4.5 @@ -20415,7 +20425,7 @@ snapshots: ethereumjs-block@1.7.1: dependencies: - async: 2.6.2 + async: 2.6.4 ethereum-common: 0.2.0 ethereumjs-tx: 1.3.7 ethereumjs-util: 5.2.1 @@ -20500,7 +20510,7 @@ snapshots: ethereumjs-vm@2.6.0: dependencies: - async: 2.6.2 + async: 2.6.4 async-eventemitter: 0.2.4 ethereumjs-account: 2.0.5 ethereumjs-block: 2.2.2 @@ -22861,7 +22871,7 @@ snapshots: json-rpc-engine@3.8.0: dependencies: - async: 2.6.2 + async: 2.6.4 babel-preset-env: 1.7.0 babelify: 7.3.0 json-rpc-error: 2.0.0