Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions subgraph/scripts/generate-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ if (shouldGenerateYaml) {
try {
fs.writeFileSync(outputPath, yamlContent);
console.log(`✅ Generated subgraph.yaml for ${network} network at: ${outputPath}`);
console.log(` Source: service_contracts/deployments.json`);
} catch (error) {
console.error(`Error: Failed to write subgraph.yaml to: ${outputPath}`);
console.error(`Write Error: ${error.message}`);
Expand Down
3 changes: 2 additions & 1 deletion subgraph/scripts/generate-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const requiredContracts = ["PDPVerifier", "ServiceProviderRegistry", "FilecoinWa
for (const contract of requiredContracts) {
if (!selectedConfig[contract] || !selectedConfig[contract].address) {
console.error(`Error: Missing or invalid '${contract}' configuration for network '${network}'`);
console.error(`Each contract must have an 'address' field in config/network.json`);
console.error(`Contract must have an 'address' field in service_contracts/deployments.json`);
process.exit(1);
}
}
Expand Down Expand Up @@ -50,6 +50,7 @@ try {

fs.writeFileSync(outputPath, constantsContent);
console.log(`✅ Generated constants for ${network} network at: ${outputPath}`);
console.log(` Source: service_contracts/deployments.json`);
} catch (error) {
console.error(`Error: Failed to write constants file to: ${outputPath}`);
console.error(`Write Error: ${error.message}`);
Expand Down
206 changes: 185 additions & 21 deletions subgraph/scripts/utils/config-loader.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,212 @@
const fs = require("fs");
const path = require("path");

// Network name to chain ID mapping
const NETWORK_CHAIN_IDS = {
mainnet: "314",
calibration: "314159",
};

// Network name to subgraph network name mapping
const NETWORK_NAMES = {
mainnet: "filecoin",
calibration: "filecoin-testnet",
};

// Mapping from deployments.json keys to template keys
const ADDRESS_MAPPING = {
PDP_VERIFIER_PROXY_ADDRESS: "PDPVerifier",
SERVICE_PROVIDER_REGISTRY_PROXY_ADDRESS: "ServiceProviderRegistry",
FWSS_PROXY_ADDRESS: "FilecoinWarmStorageService",
FILECOIN_PAY_ADDRESS: "USDFCToken",
};

// Default start blocks for each network (can be overridden via environment or config)
// These represent the approximate deployment blocks for the contracts
const DEFAULT_START_BLOCKS = {
mainnet: {
PDPVerifier: 1000000,
ServiceProviderRegistry: 1000000,
FilecoinWarmStorageService: 1000000,
USDFCToken: 1000000,
},
calibration: {
PDPVerifier: 2988297,
ServiceProviderRegistry: 2988311,
FilecoinWarmStorageService: 2988329,
USDFCToken: 2988000,
},
};

/**
* Loads and validates network configuration from config/network.json
* @param {string} network - The network name to load
* @returns {Object} The network configuration object
* Loads deployment addresses from service_contracts/deployments.json
* @returns {Object} The parsed deployments object
*/
function loadNetworkConfig(network = "calibration") {
const configPath = path.join(__dirname, "..", "..", "config", "network.json");
let networkConfig;
function loadDeployments() {
const deploymentsPath = path.join(
__dirname,
"..",
"..",
"..",
"service_contracts",
"deployments.json"
);

try {
const configContent = fs.readFileSync(configPath, "utf8");
networkConfig = JSON.parse(configContent);
const content = fs.readFileSync(deploymentsPath, "utf8");
return JSON.parse(content);
} catch (error) {
if (error.code === "ENOENT") {
console.error(`Error: Configuration file not found at: ${configPath}`);
console.error("Please ensure config/network.json exists in your project.");
console.error(`Error: Deployments file not found at: ${deploymentsPath}`);
console.error(
"Please ensure service_contracts/deployments.json exists."
);
process.exit(1);
}
if (error instanceof SyntaxError) {
console.error(`Error: Invalid JSON in configuration file: ${configPath}`);
console.error("Please check that config/network.json contains valid JSON.");
console.error(
`Error: Invalid JSON in deployments file: ${deploymentsPath}`
);
console.error(`JSON Error: ${error.message}`);
} else {
console.error(`Error reading configuration file: ${configPath}`);
console.error(`Error reading deployments file: ${deploymentsPath}`);
console.error(`File Error: ${error.message}`);
}
process.exit(1);
}
}

/**
* Loads optional start block overrides from config/start-blocks.json
* @param {string} network - The network name
* @returns {Object|null} The start blocks object or null if not found
*/
function loadStartBlockOverrides(network) {
const overridesPath = path.join(
__dirname,
"..",
"..",
"config",
"start-blocks.json"
);

try {
const content = fs.readFileSync(overridesPath, "utf8");
const overrides = JSON.parse(content);
return overrides[network] || null;
} catch {
// Start block overrides are optional
return null;
}
}

/**
* Loads and validates network configuration from service_contracts/deployments.json
* @param {string} network - The network name to load ("mainnet" or "calibration")
* @returns {Object} The network configuration object formatted for templates
*/
function loadNetworkConfig(network = "calibration") {
const chainId = NETWORK_CHAIN_IDS[network];

if (!networkConfig.networks) {
console.error("Error: Invalid configuration structure. Missing 'networks' object in config/network.json");
console.error('Expected structure: { "networks": { "calibration": {...}, "mainnet": {...} } }');
if (!chainId) {
console.error(`Error: Unknown network '${network}'`);
console.error(
`Available networks: ${Object.keys(NETWORK_CHAIN_IDS).join(", ")}`
);
process.exit(1);
}

if (!networkConfig.networks[network]) {
console.error(`Error: Network '${network}' not found in config/network.json`);
console.error(`Available networks: ${Object.keys(networkConfig.networks).join(", ")}`);
const deployments = loadDeployments();
const networkDeployments = deployments[chainId];

if (!networkDeployments) {
console.error(
`Error: No deployments found for chain ID ${chainId} (network: ${network})`
);
console.error(`Available chain IDs: ${Object.keys(deployments).join(", ")}`);
process.exit(1);
}

return networkConfig.networks[network];
// Load start block overrides (optional)
const startBlockOverrides = loadStartBlockOverrides(network);
const defaultStartBlocks = DEFAULT_START_BLOCKS[network] || {};

// Build the configuration object expected by templates
const config = {
name: NETWORK_NAMES[network],
Comment on lines +135 to +136
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

If a network is added to NETWORK_CHAIN_IDS but not to NETWORK_NAMES, the config object will have name: undefined, which could cause issues with template rendering. Consider adding validation to ensure both mappings are in sync, or consolidating them into a single data structure.

For example:

const NETWORK_CONFIG = {
  mainnet: { chainId: "314", subgraphName: "filecoin" },
  calibration: { chainId: "314159", subgraphName: "filecoin-testnet" }
};

This would make it impossible to have mismatched entries.

Copilot uses AI. Check for mistakes.
};

// Map deployment addresses to template format
for (const [deploymentKey, templateKey] of Object.entries(ADDRESS_MAPPING)) {
const address = networkDeployments[deploymentKey];

if (!address) {
console.error(
`Error: Missing '${deploymentKey}' in deployments.json for chain ID ${chainId}`
);
process.exit(1);
}

// Get start block from overrides, defaults, or fallback
const startBlock =
startBlockOverrides?.[templateKey] ||
defaultStartBlocks[templateKey] ||
0;

Comment on lines +151 to +155
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The fallback value of 0 for startBlock (line 154) means the subgraph will start indexing from the genesis block if no override or default is provided. This could result in inefficient indexing for contracts deployed much later. Consider logging a warning when falling back to 0, or making it a more reasonable default value based on known deployment timing.

For reference, mainnet (chain ID 314) launched at block 0 in 2021, and calibration (chain ID 314159) also starts at block 0. Having a fallback of 0 means potentially scanning millions of blocks before reaching the actual contract deployments.

Suggested change
const startBlock =
startBlockOverrides?.[templateKey] ||
defaultStartBlocks[templateKey] ||
0;
let startBlock;
if (
startBlockOverrides &&
Object.prototype.hasOwnProperty.call(startBlockOverrides, templateKey)
) {
startBlock = startBlockOverrides[templateKey];
} else if (
defaultStartBlocks &&
Object.prototype.hasOwnProperty.call(defaultStartBlocks, templateKey)
) {
startBlock = defaultStartBlocks[templateKey];
} else {
console.warn(
`Warning: No startBlock configured for '${templateKey}' on network '${network}' (chain ID ${chainId}); falling back to 0. ` +
"This may cause slow indexing if the contract was deployed well after genesis."
);
startBlock = 0;
}

Copilot uses AI. Check for mistakes.
config[templateKey] = {
address: address,
startBlock: startBlock,
};
}

return config;
}

/**
* Gets the path to an ABI file
* @param {string} contractName - The contract name (e.g., "PDPVerifier")
* @returns {string} The absolute path to the ABI file
*/
function getAbiPath(contractName) {
return path.join(
__dirname,
"..",
"..",
"..",
"service_contracts",
"abi",
`${contractName}.abi.json`
);
}

/**
* Loads an ABI from service_contracts/abi/
* @param {string} contractName - The contract name (e.g., "PDPVerifier")
* @returns {Array} The parsed ABI array
*/
function loadAbi(contractName) {
const abiPath = getAbiPath(contractName);

try {
const content = fs.readFileSync(abiPath, "utf8");
return JSON.parse(content);
} catch (error) {
if (error.code === "ENOENT") {
console.error(`Error: ABI file not found at: ${abiPath}`);
console.error(
`Please ensure service_contracts/abi/${contractName}.abi.json exists.`
);
process.exit(1);
}
if (error instanceof SyntaxError) {
console.error(`Error: Invalid JSON in ABI file: ${abiPath}`);
console.error(`JSON Error: ${error.message}`);
} else {
console.error(`Error reading ABI file: ${abiPath}`);
console.error(`File Error: ${error.message}`);
}
process.exit(1);
}
}
Comment on lines +187 to 210
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The loadAbi function expects ABI files to be named using the template key format (e.g., "USDFCToken.abi.json"), but the actual ABI file for the USDFCToken contract is named "FilecoinPayV1.abi.json" in the service_contracts/abi directory. While this function is currently not used in the codebase, it could cause runtime errors if someone tries to call loadAbi("USDFCToken") in the future.

Consider either:

  1. Documenting that loadAbi should use the actual contract names (e.g., "FilecoinPayV1") rather than the template keys
  2. Creating a mapping from template keys to actual ABI file names
  3. Ensuring ABI files are named consistently with the template keys

Copilot uses AI. Check for mistakes.

module.exports = { loadNetworkConfig };
module.exports = { loadNetworkConfig, loadDeployments, loadAbi, getAbiPath };
2 changes: 1 addition & 1 deletion subgraph/templates/constants.template.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// This file is auto-generated. Do not edit manually.
// Generated from config/network.json for network: {{network}}
// Generated from service_contracts/deployments.json for network: {{network}}
// Last generated: {{timestamp}}

import { Address, Bytes } from "@graphprotocol/graph-ts";
Expand Down
Loading