Skip to content
4,400 changes: 4,004 additions & 396 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
"dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
"@playwright/test": "^1.55.0",
"@reown/appkit": "^1.8.16",
"@reown/appkit-adapter-wagmi": "^1.8.16",
"@scure/bip39": "^1.4.0",
"@wagmi/connectors": "^5.3.3",
"@wagmi/core": "^2.7.0",
"@wagmi/core": "^2.22.1",
"@web3modal/wagmi": "^4.1.11",
"canvas": "^3.1.0",
"chart.js": "^4.4.4",
Expand All @@ -68,11 +70,13 @@
"packery": "^2.1.2",
"popper.js": "^1.16.1",
"qrcode": "^1.5.4",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"scure": "^1.6.0",
"sharp": "^0.33.5",
"spotlight.js": "^0.7.8",
"tippy.js": "^6.3.7",
"viem": "^2.9.28",
"viem": "^2.44.2",
"web3": "^4.10.0",
"webamp": "^2.2.0",
"webpack-merge": "^5.10.0"
Expand Down
70 changes: 60 additions & 10 deletions src/build_logic/chain_reading.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,22 @@ import { setStoneContractAddress, blueRailroadContractAddress, AVERAGE_BLOCK_TIM
import {ticketStubClaimerABI} from "../abi/ticketStubClaimerABI.js";
import {getVowelsoundContributions} from "./revealer_utils.js";
import Web3 from 'web3';
import enumerableContracts from './enumerable_contracts.json' with { type: 'json' };

const web3 = new Web3();
import { config as dotenvConfig } from 'dotenv';
import {fileURLToPath} from "url";

// Check if a contract is enabled in the enumerable contracts config
function isContractEnabled(name) {
const contract = enumerableContracts.contracts.find(c => c.name === name);
return contract && !contract.disabled;
}

// Get contract config by name
function getContractConfig(name) {
return enumerableContracts.contracts.find(c => c.name === name);
}
import path from "path";
import fs from "fs";
import { getProjectDirs } from "./locations.js";
Expand Down Expand Up @@ -341,12 +353,40 @@ export async function getBlueRailroads(config) {
args: [tokenId],
});

// Fetch songId for this token
let songId = await readContract(config, {
abi,
address: blueRailroadContractAddress,
functionName: 'tokenIdToSongId',
chainId: optimism.id,
args: [tokenId],
});

// Fetch date for this token (YYYYMMDD format)
let date = await readContract(config, {
abi,
address: blueRailroadContractAddress,
functionName: 'tokenIdToDate',
chainId: optimism.id,
args: [tokenId],
});

// Resolve ENS name for owner (falls back to address if no ENS)
let ownerDisplay = await fetchEnsName(config, { address: ownerOfThisToken, chainId: 1 });
if (ownerDisplay === undefined || ownerDisplay === null) {
ownerDisplay = ownerOfThisToken;
}

blueRailroads[tokenId] = {
id: tokenId,
owner: ownerOfThisToken,
ownerDisplay: ownerDisplay,
uri: uriOfVideo,
id: tokenId
songId: songId,
date: date
};

console.log(`Blue Railroad #${tokenId}: song ${songId}, date ${date}, owner ${ownerDisplay}`);
}
console.timeEnd("Blue Railroads (listen to that old smokestack)");
return blueRailroads;
Expand Down Expand Up @@ -440,20 +480,30 @@ export async function fetch_chaindata(shows) {
const optimismSepoliaBlockNumber = await fetchBlockNumber(config, {chainId: optimismSepolia.id});
console.timeEnd("Block Heights");

const blueRailroads = await getBlueRailroads(config);
let showsWithChainData = await fetchChainDataForShows(shows, config);
let showsWithSetStoneData = await appendSetStoneDataToShows(showsWithChainData, config);
const vowelSoundContributions = await getVowelsoundContributions(config);


// Fetch data for enabled enumerable contracts
const chainData = {
blueRailroads: blueRailroads,
mainnetBlockNumber: mainnetBlockNumber,
optimismBlockNumber: optimismBlockNumber,
optimismSepoliaBlockNumber: optimismSepoliaBlockNumber,
showsWithChainData: showsWithSetStoneData,
vowelSoundContributions: vowelSoundContributions,
// Track which contracts were fetched
enumeratedContracts: enumerableContracts.contracts.filter(c => !c.disabled).map(c => c.name),
};

if (isContractEnabled('BlueRailroad')) {
console.log('Fetching Blue Railroad tokens...');
chainData.blueRailroads = await getBlueRailroads(config);
}

if (isContractEnabled('SetStone')) {
console.log('Fetching SetStone data...');
let showsWithChainData = await fetchChainDataForShows(shows, config);
chainData.showsWithChainData = await appendSetStoneDataToShows(showsWithChainData, config);
}

// Vowel sound contributions (not an enumerable contract, but included in chain data)
const vowelSoundContributions = await getVowelsoundContributions(config);
chainData.vowelSoundContributions = vowelSoundContributions;

return chainData;
}

Expand Down
24 changes: 24 additions & 0 deletions src/build_logic/enumerable_contracts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"description": "Contracts that should be enumerated during chain data fetch. Each is assumed to be ERC721Enumerable.",
"contracts": [
{
"name": "BlueRailroad",
"chainId": 10,
"address": "0xCe09A2d0d0BDE635722D8EF31901b430E651dB52",
"description": "Blue Railroad Train exercise tokens on Optimism"
},
{
"name": "SetStone",
"chainId": 42161,
"address": "0x39269b3FddFc9bf0626e5CFe4424aa51A77f7678",
"description": "Set Stone NFTs on Arbitrum"
},
{
"name": "TicketStubClaimer",
"chainId": 42161,
"address": "0x1234567890123456789012345678901234567890",
"description": "Ticket stub NFTs on Arbitrum (placeholder address)",
"disabled": true
}
]
}
11 changes: 5 additions & 6 deletions src/build_logic/fetch_video_metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,21 @@ dotenv.config();
async function getBlueRailroadMetadata() {

let apiKey = process.env.ALCHEMY_API_KEY;
let apiUri;
// Bail if the API key is not set.
if (apiKey && apiKey !== "") {
apiUri = `https://opt-mainnet.g.alchemy.com/v2/${apiKey}`;
} else {
if (!apiKey || apiKey === "") {
throw new Error('The API key is not set in the environment variables.');
}

const { fetchedAssetsDir } = getProjectDirs();
const spinner = ora('Reading Blue Railroad contract data').start();

try {
// Need both optimism (for Blue Railroad contract) and mainnet (for ENS resolution)
const config = createConfig({
chains: [optimism],
chains: [mainnet, optimism],
transports: {
[optimism.id]: http(apiUri)
[mainnet.id]: http(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`),
[optimism.id]: http(`https://opt-mainnet.g.alchemy.com/v2/${apiKey}`)
}
});

Expand Down
36 changes: 35 additions & 1 deletion src/build_logic/pickipedia_submissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,37 @@
const PICKIPEDIA_API = 'https://pickipedia.xyz/api.php';
const PICKIPEDIA_WIKI = 'https://pickipedia.xyz/wiki';

/**
* Get the actual URL for a file from MediaWiki API
* MediaWiki stores files in hash-based subdirectories, so we need to query the API
*/
async function getFileUrl(filename) {
if (!filename) return null;

// MediaWiki file titles need "File:" prefix
const fileTitle = filename.startsWith('File:') ? filename : `File:${filename}`;

const url = `${PICKIPEDIA_API}?action=query&titles=${encodeURIComponent(fileTitle)}&prop=imageinfo&iiprop=url&format=json`;

try {
const response = await fetch(url);
if (!response.ok) return null;

const data = await response.json();
const pages = data.query?.pages;
if (!pages) return null;

const pageId = Object.keys(pages)[0];
if (pageId === '-1') return null;

const imageInfo = pages[pageId].imageinfo?.[0];
return imageInfo?.url || null;
} catch (e) {
console.warn(`Failed to get file URL for ${filename}:`, e.message);
return null;
}
}

/**
* Fetch a single submission page from PickiPedia
*/
Expand Down Expand Up @@ -102,12 +133,15 @@ export async function fetchPendingSubmissions() {
continue;
}

// Get the actual file URL from MediaWiki API (handles hash-based subdirectories)
const videoUrl = await getFileUrl(parsed.video);

const submission = {
id: i,
url: `${PICKIPEDIA_WIKI}/Blue_Railroad_Submission/${i}`,
exercise: parsed.exercise,
video: parsed.video,
videoUrl: parsed.video ? `https://pickipedia.xyz/images/${parsed.video}` : null,
videoUrl: videoUrl,
blockHeight: parsed.blockHeight,
status: parsed.status,
participants: parsed.participants,
Expand Down
1 change: 0 additions & 1 deletion src/build_logic/setstone_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ export function generateSetStonePages(shows, outputDir) {
tokenId: ticketStub.tokenId,
contractAddress: ticketStubClaimerContractAddress,
contractABI: JSON.stringify(ticketStubClaimerABI),
alchemyApiKey: process.env.ALCHEMY_API_KEY,
show: show,
ticketStub: ticketStub,
};
Expand Down
12 changes: 12 additions & 0 deletions src/build_logic/webpack.cryptograss.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ function buildHtmlPluginInstances() {

if (relativePath.startsWith('tools/oracle-of-bluegrass-bacon')) {
var chunks = ['main', 'oracle_client'];
} else if (relativePath.startsWith('blox-office/admin/mint/')) {
var chunks = ['main', 'mint_submission'];
} else {
var chunks = ['main'];
}
Expand Down Expand Up @@ -100,6 +102,7 @@ export function buildConfig() {
shapes: `${frontendJSDir}/shapes.js`,
blue_railroad: `${frontendJSDir}/bazaar/blue_railroad.js`,
oracle_client: `${frontendJSDir}/oracle_client.js`,
mint_submission: `${frontendJSDir}/mint-submission.js`,
},
module: {
rules: [
Expand All @@ -109,6 +112,15 @@ export function buildConfig() {
},
]
},
resolve: {
fallback: {
// Optional wagmi connector dependencies - not needed for basic wallet connection
'@base-org/account': false,
'@gemini-wallet/core': false,
'porto': false,
'porto/internal': false,
}
},
};
}

Expand Down
Loading