diff --git a/.github/workflows/generate-aggregates.yml b/.github/workflows/generate-aggregates.yml deleted file mode 100644 index b196b48..0000000 --- a/.github/workflows/generate-aggregates.yml +++ /dev/null @@ -1,50 +0,0 @@ -# This workflow runs scripts/ProtocolsTable.py and commits protocols.csv to this branch - -name: Generate CSV and JSON files - -on: - push: - branches: [ main, master ] - -jobs: - generate-csv: - runs-on: ubuntu-latest - - permissions: - # Give the default GITHUB_TOKEN write permission to commit and push the - # added or changed files to the repository. - contents: write - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.14' - - - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 - with: - enable-cache: true - - - name: Install dependencies - run: uv sync - - - name: Run script (testnet) - run: | - source .venv/bin/activate - python scripts/ProtocolsTable.py --network testnet - - - name: Run script (mainnet) - run: | - source .venv/bin/activate - python scripts/ProtocolsTable.py --network mainnet - - # Commit all changed files back to the repository - - uses: stefanzweifel/git-auto-commit-action@v7 - with: - commit_message: "Update protocols CSV and JSON files" - file_pattern: "protocols-*.csv protocols-*.json" - push_options: "--force" diff --git a/.github/workflows/generate-and-publish.yml b/.github/workflows/generate-and-publish.yml new file mode 100644 index 0000000..07d4dc2 --- /dev/null +++ b/.github/workflows/generate-and-publish.yml @@ -0,0 +1,119 @@ +# This workflow regenerates the protocol aggregates (protocols-*.csv/json), +# commits them back to the branch, and then publishes the data as an npm package. +# +# Publishing uses npm Trusted Publishing (OIDC), renaming this workflow file +# breaks the trust relationship. + +name: generate and publish + +on: + push: + branches: [ main, master ] + +# Serialize runs so two pushes can't race on the registry +concurrency: + group: generate-and-publish + cancel-in-progress: false + +jobs: + generate-and-publish: + runs-on: ubuntu-latest + + # Pushes made with the default GITHUB_TOKEN (like our auto-commit below) + # don't re-trigger workflows + if: github.event.head_commit.message != 'Update protocols CSV and JSON files' + + permissions: + # Give the default GITHUB_TOKEN write permission to commit and push the + # added or changed files to the repository. + contents: write + # Required for npm provenance: lets the job mint a short-lived OIDC token + # that Sigstore uses to sign the release. + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.14' + + - name: Install uv + uses: astral-sh/setup-uv@v8.1.0 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync + + - name: Run script (testnet) + run: | + source .venv/bin/activate + python scripts/ProtocolsTable.py --network testnet + + - name: Run script (mainnet) + run: | + source .venv/bin/activate + python scripts/ProtocolsTable.py --network mainnet + + # Commit all changed files back to the repository. The commit sits on + # top of the SHA that triggered this run + - uses: stefanzweifel/git-auto-commit-action@v7 + with: + commit_message: "Update protocols CSV and JSON files" + file_pattern: "protocols-*.csv protocols-*.json" + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: 'https://registry.npmjs.org' + + - name: Set version from run number + run: | + # Keep major.minor from package.json, use the run number as the patch + # so every push to main produces a unique, monotonic release. + BASE=$(node -p "require('./package.json').version.split('.').slice(0, 2).join('.')") + VERSION="${BASE}.${GITHUB_RUN_NUMBER}" + echo "Publishing version ${VERSION}" + npm version "${VERSION}" --no-git-tag-version --allow-same-version + + - name: Build npm package + run: npm run build + + - name: Verify bundled files and casing + working-directory: dist + run: | + npm pack --dry-run --json > pack.json + node -e ' + const fs = require("fs"), path = require("path"); + const packed = new Set(JSON.parse(fs.readFileSync("pack.json", "utf8"))[0].files.map(f => f.path)); + const walk = d => fs.readdirSync(d, { withFileTypes: true }) + .flatMap(e => e.isDirectory() ? walk(path.join(d, e.name)) : [path.join(d, e.name)]); + const expected = [ + "mainnet.json", "testnet.json", "mainnet.csv", "testnet.csv", "categories.json", + ...walk("mainnet"), ...walk("testnet"), + ].filter(p => /\.(json|jsonc|csv)$/.test(p)); + const missing = expected.filter(p => !packed.has(p)); + if (missing.length) { + console.error("Files missing from the package (often a casing mismatch):\n " + missing.join("\n ")); + process.exit(1); + } + // Guard against case-only collisions that break case-insensitive filesystems. + const seen = new Map(); + for (const p of packed) { + const key = p.toLowerCase(); + if (seen.has(key) && seen.get(key) !== p) { + console.error(`Case collision: "${seen.get(key)}" vs "${p}"`); + process.exit(1); + } + seen.set(key, p); + } + console.log(`Verified ${expected.length} data files bundled.`); + ' + + - name: Publish to npm + working-directory: dist + run: npm publish diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..78704bd --- /dev/null +++ b/index.d.ts @@ -0,0 +1,34 @@ +/** + * A category::sub-category pair, e.g. "DeFi::DEX". + */ +export type Category = string; + +/** + * A single protocol entry, keyed by its slug in the aggregate maps. + */ +export interface Protocol { + /** Display name of the protocol. */ + name: string; + /** Human-readable description. */ + description: string; + /** Whether the protocol is live, when present in the source data. */ + live?: boolean; + /** Category::sub-category pairs, primary first. */ + categories: Category[]; + /** Mapping of contract name -> address. */ + addresses: Record; + /** Mapping of link type (project, twitter, github, docs, ...) -> URL. */ + links: Record; +} + +/** Map of protocol slug -> protocol entry. */ +export type ProtocolMap = Record; + +/** Mainnet protocols, keyed by slug. */ +export declare const mainnet: ProtocolMap; + +/** Testnet protocols, keyed by slug. */ +export declare const testnet: ProtocolMap; + +/** The full list of valid category::sub-category values. */ +export declare const categories: { categories: Category[] }; diff --git a/index.js b/index.js new file mode 100644 index 0000000..2ebc4ef --- /dev/null +++ b/index.js @@ -0,0 +1,30 @@ +'use strict'; + +/** + * Aggregated Monad protocol data, bundled from the monad-crypto/protocols + * repository. The individual per-protocol files are also shipped in this + * package and can be imported directly, e.g.: + * + * const protocols = require('@monad-crypto/protocols'); + * protocols.mainnet['0x']; + * + * // or a single raw file (per-protocol casing is preserved): + * const kuru = require('@monad-crypto/protocols/mainnet/kuru.jsonc'); + * + * CSV summaries are shipped as files too and can be resolved via: + * require.resolve('@monad-crypto/protocols/mainnet.csv'); + * + * NOTE: the aggregate files referenced below are produced at bundle time by + * scripts/build-npm.mjs (the in-repo sources are protocols-{mainnet,testnet}.json). + * Run `npm run build` before requiring this file outside the published package. + */ + +const mainnet = require('./mainnet.json'); +const testnet = require('./testnet.json'); +const categories = require('./categories.json'); + +module.exports = { + mainnet, + testnet, + categories, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..8563ca5 --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "@monad-crypto/protocols", + "version": "0.1.0", + "description": "Contract addresses, links, categories, and metadata for protocols on the Monad network.", + "homepage": "https://github.com/monad-crypto/protocols#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/monad-crypto/protocols.git" + }, + "bugs": { + "url": "https://github.com/monad-crypto/protocols/issues" + }, + "main": "index.js", + "types": "index.d.ts", + "exports": { + ".": { + "types": "./index.d.ts", + "default": "./index.js" + }, + "./categories.json": "./categories.json", + "./mainnet.json": "./mainnet.json", + "./testnet.json": "./testnet.json", + "./mainnet.csv": "./mainnet.csv", + "./testnet.csv": "./testnet.csv", + "./mainnet/*": "./mainnet/*", + "./testnet/*": "./testnet/*", + "./package.json": "./package.json" + }, + "files": [ + "index.js", + "index.d.ts", + "categories.json", + "mainnet.json", + "testnet.json", + "mainnet.csv", + "testnet.csv", + "mainnet", + "testnet" + ], + "scripts": { + "build": "node scripts/build-npm.mjs" + }, + "keywords": [ + "monad", + "protocols", + "contracts", + "addresses", + "web3", + "defi", + "token-list" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + } +} diff --git a/scripts/build-npm.mjs b/scripts/build-npm.mjs new file mode 100644 index 0000000..087055e --- /dev/null +++ b/scripts/build-npm.mjs @@ -0,0 +1,40 @@ +// Assembles the publishable npm package under dist/. + +import { cpSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..'); +const dist = join(root, 'dist'); + +rmSync(dist, { recursive: true, force: true }); +mkdirSync(dist, { recursive: true }); + +// Generated aggregates -> JS-package-friendly names +const aggregates = { + 'protocols-mainnet.json': 'mainnet.json', + 'protocols-testnet.json': 'testnet.json', + 'protocols-mainnet.csv': 'mainnet.csv', + 'protocols-testnet.csv': 'testnet.csv', +}; +for (const [src, dest] of Object.entries(aggregates)) { + cpSync(join(root, src), join(dist, dest)); +} + +// Files shipped under their existing names. +for (const f of ['categories.json', 'index.js', 'index.d.ts', 'README.md']) { + cpSync(join(root, f), join(dist, f)); +} + +// The published package is pre-built data: drop the scripts field so its +// metadata doesn't reference build tooling that isn't shipped. +const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')); +delete pkg.scripts; +writeFileSync(join(dist, 'package.json'), JSON.stringify(pkg, null, 2) + '\n'); + +// Per-protocol directories, copied verbatim +for (const dir of ['mainnet', 'testnet']) { + cpSync(join(root, dir), join(dist, dir), { recursive: true }); +} + +console.log('Built dist/ npm package');