Skip to content
Merged
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
86 changes: 86 additions & 0 deletions .github/workflows/check-contracts-abi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Check ABI Up-to-Date

on:
pull_request:
paths:
- "contracts/**"
- ".github/workflows/check-contracts-abi.yml"
push:
branches:
- dev
- main
paths:
- "contracts/**"
- ".github/workflows/check-contracts-abi.yml"

jobs:
check-abi:
name: 🔍 Verify ABIs are up-to-date
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./contracts

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: contracts/package.json

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "pnpm"
cache-dependency-path: contracts/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Compile contracts and generate ABIs
run: pnpm compile

- name: Check for ABI differences
run: |
if [ -n "$(git diff --name-only -- abi/)" ]; then
echo "::error::Generated ABIs do not match committed ABIs. Please run 'pnpm compile' in contracts/ and commit the updated ABI files."
echo ""
echo "Changed files:"
git diff --name-only -- abi/
echo ""
echo "Diff:"
git diff -- abi/
exit 1
fi

if [ -n "$(git ls-files --others --exclude-standard -- abi/)" ]; then
echo "::error::New ABI files were generated but not committed. Please run 'pnpm compile' in contracts/ and commit the new ABI files."
echo ""
echo "Untracked files:"
git ls-files --others --exclude-standard -- abi/
exit 1
fi

echo "✅ All ABIs are up-to-date."

- name: Summary
if: failure()
run: |
echo "## ❌ ABI Check Failed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The committed ABI files do not match what the contracts generate." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**To fix:** Run the following locally and commit the changes:" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "cd contracts && pnpm compile" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

- name: Summary (success)
if: success()
run: |
echo "## ✅ ABI Check Passed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All generated ABIs match the committed versions." >> $GITHUB_STEP_SUMMARY
139 changes: 139 additions & 0 deletions .github/workflows/publish-collateral-abi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Publish @hashpower/collateral-abi

# Publishes the ABI package to npm whenever the generated ABIs or the
# deployments manifest change on main. Versioning is automatic and
# semver-correct: the ABI *is* the package's public API, so CI diffs the
# built ABI surface against the last published version to compute the bump
# (removed/changed entry -> major, added entry -> minor, metadata -> patch)
# and commits the new version back before publishing.
#
# Auth uses npm Trusted Publishing (OIDC) — no NPM_TOKEN secret.
# NOTE: the very first release must be published manually
# (`cd collateral-abi && pnpm build && npm publish --access public`),
# then configure this repo+workflow as a Trusted Publisher in the
# package settings on npmjs.com. CI handles every release after that.

on:
push:
branches:
# NOTE: publishing off `dev` while the repo operates there.
# At GA, change this to `main` (and update the `npm-publish`
# environment's deployment-branch rule to match) so npm only
# updates on mainline releases.
- dev
paths:
- "contracts/abi/**"
- "collateral-abi/**"
- ".github/workflows/publish-collateral-abi.yml"
# Manual runs from any branch for testing the pipeline
workflow_dispatch:

permissions:
contents: write # push the automated version bump
id-token: write # npm provenance / trusted publishing

concurrency:
group: publish-collateral-abi
cancel-in-progress: false

jobs:
publish:
name: 📦 Build & publish to npm
runs-on: ubuntu-latest
# Must match the Trusted Publisher environment configured on npmjs.com
environment: npm-publish
defaults:
run:
working-directory: ./collateral-abi

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: collateral-abi/package.json

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
cache: "pnpm"
cache-dependency-path: collateral-abi/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build package
run: pnpm build

- name: Compute semver bump from ABI diff
id: bump
run: |
PKG=$(node -p "require('./package.json').name")
PUBLISHED=$(npm view "$PKG" version 2>/dev/null || echo "none")

if [ "$PUBLISHED" = "none" ]; then
echo "First publish — using version from package.json as-is"
echo "level=first" >> "$GITHUB_OUTPUT"
echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
exit 0
fi

# Fetch the published tarball and diff its ABI surface against the fresh build
TARBALL=$(npm pack "$PKG@$PUBLISHED" --silent | tail -1)
mkdir -p /tmp/published
tar -xzf "$TARBALL" -C /tmp/published
rm "$TARBALL"

LEVEL=$(node scripts/semver-diff.mjs /tmp/published/package .)
echo "Published: $PUBLISHED — ABI diff requires: $LEVEL"
echo "level=$LEVEL" >> "$GITHUB_OUTPUT"

if [ "$LEVEL" = "none" ]; then
echo "No ABI or metadata changes — skipping publish"
echo "version=$PUBLISHED" >> "$GITHUB_OUTPUT"
exit 0
fi

# Baseline on the published version, then apply the computed bump
npm version "$PUBLISHED" --no-git-tag-version --allow-same-version
npm version "$LEVEL" --no-git-tag-version
NEW=$(node -p "require('./package.json').version")

if ! git diff --quiet -- package.json; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add package.json
git commit -m "chore(collateral-abi): v$NEW ($LEVEL) [skip ci]"
git push
fi
echo "version=$NEW" >> "$GITHUB_OUTPUT"
echo "Version: $NEW ($LEVEL bump from $PUBLISHED)"

- name: Publish
if: steps.bump.outputs.level != 'none'
run: npm publish --access public --provenance

- name: Summary
if: steps.bump.outputs.level != 'none'
run: |
PKG=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
echo "## 📦 Published $PKG@$VERSION" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "https://www.npmjs.com/package/$PKG/v/$VERSION" >> $GITHUB_STEP_SUMMARY

- name: Send Slack notification
if: always() && steps.bump.outputs.level != 'none' && steps.bump.outputs.level != ''
uses: ./.github/actions/slack-notify
with:
status: ${{ job.status }}
environment: "npm"
service_name: "@hashpower/collateral-abi"
version: ${{ steps.bump.outputs.version || 'unknown' }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
github_token: ${{ secrets.GITHUB_TOKEN }}
additional_info: "${{ format('*Semver bump:* `{0}` • *Package:* <https://www.npmjs.com/package/@hashpower/collateral-abi/v/{1}|@hashpower/collateral-abi@{1}>', steps.bump.outputs.level, steps.bump.outputs.version) }}"
5 changes: 5 additions & 0 deletions collateral-abi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Generated by scripts/build.mjs — source of truth is ../contracts/abi
src/
dist/
json/
node_modules/
40 changes: 40 additions & 0 deletions collateral-abi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# @hashpower/collateral-abi

ABIs and deployment addresses for the Hashpower unified collateral system on Base:

| Contract | Purpose |
| --- | --- |
| `CollateralVault` | Shared USDC custody — `deposit`, `withdraw` (IM-gated), `balanceOf` |
| `PortfolioMarginEngine` | Cross-product portfolio margin — `computePortfolioIM/MM`, `isHealthy`, `canPlaceOrder` |
| `Points` | Rewards ledger |

Collateral is unified across all Hashpower trading venues (futures, perps): deposit once to the vault, trade everywhere. Withdrawals are gated by portfolio initial margin.

## Usage

```ts
import { CollateralVaultAbi, PortfolioMarginEngineAbi } from "@hashpower/collateral-abi";
import deployments from "@hashpower/collateral-abi/deployments.json" with { type: "json" };

// "testnet" (Base Sepolia) or "mainnet" (Base)
const env = process.env.HASHPOWER_ENV ?? "testnet";
const { contracts } = deployments.environments[env];

const im = await client.readContract({
address: contracts.PortfolioMarginEngine,
abi: PortfolioMarginEngineAbi,
functionName: "computePortfolioIM",
args: [account],
});
```

Raw JSON ABIs (for subgraphs and non-TypeScript consumers) are available under `@hashpower/collateral-abi/json/<Contract>.json`.

## How this package is built

Contents are generated — do not edit by hand:

- `src/` is copied from `../contracts/abi` (the Hardhat codegen output) by `scripts/build.mjs`, then compiled to `dist/`.
- `deployments.json` is the canonical address manifest for this repo; it is updated when contracts are (re)deployed.

Publishing happens automatically from CI when ABIs or the manifest change (see `.github/workflows/publish-collateral-abi.yml`).
22 changes: 22 additions & 0 deletions collateral-abi/deployments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"package": "@hashpower/collateral-abi",
"environments": {
"testnet": {
"chainId": 84532,
"network": "base-sepolia",
"contracts": {
"CollateralVault": "0x54A79e2a5C60ACe37b280eBbCda51b4E903d25F0",
"PortfolioMarginEngine": "0x3899e429Ef47140eC46c6E23F04253C24F221b69",
"Points": "0x153F6cb4386d717AD94791E6Ee8ae37f80315972",
"CollateralToken": "0xDd15eED84065A58c9E9ff9E95fb996be0fff22AA"
},
"subgraphs": {}
},
"mainnet": {
"chainId": 8453,
"network": "base",
"contracts": {},
"subgraphs": {}
}
}
}
39 changes: 39 additions & 0 deletions collateral-abi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@hashpower/collateral-abi",
"version": "1.0.0",
"description": "ABIs and deployment addresses for the Hashpower unified collateral system (CollateralVault, PortfolioMarginEngine, Points) on Base",
"license": "MIT",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/Lumerin-protocol/collateral-margin.git",
"directory": "collateral-abi"
},
"keywords": ["hashpower", "collateral", "margin", "vault", "abi", "base", "viem"],
"files": ["dist", "json", "deployments.json", "README.md"],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./deployments.json": "./deployments.json",
"./json/*.json": "./json/*.json"
},
"scripts": {
"build": "node scripts/build.mjs && tsc -p tsconfig.json",
"clean": "rm -rf src dist json",
"prepublishOnly": "pnpm build"
},
"devDependencies": {
"typescript": "^5.3.3"
},
"engines": {
"node": ">=22"
},
"packageManager": "pnpm@10.28.1",
"publishConfig": {
"access": "public"
}
}
24 changes: 24 additions & 0 deletions collateral-abi/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions collateral-abi/scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copies the codegen output from contracts/abi into this package:
// *.ts -> src/ (compiled to dist/ by tsc)
// *.json -> json/ (shipped raw for non-TS consumers, e.g. subgraphs)
// and generates src/index.ts re-exporting everything.
import { copyFileSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const abiDir = path.resolve(pkgRoot, "../contracts/abi");
const srcDir = path.join(pkgRoot, "src");
const jsonDir = path.join(pkgRoot, "json");

// Test-only mocks are not part of the public package
const EXCLUDE = new Set([]);

rmSync(srcDir, { recursive: true, force: true });
rmSync(jsonDir, { recursive: true, force: true });
mkdirSync(srcDir, { recursive: true });
mkdirSync(jsonDir, { recursive: true });

const modules = [];
for (const file of readdirSync(abiDir).sort()) {
const base = file.replace(/\.(ts|json)$/, "");
if (EXCLUDE.has(base)) continue;
if (file.endsWith(".ts")) {
copyFileSync(path.join(abiDir, file), path.join(srcDir, file));
modules.push(base);
} else if (file.endsWith(".json")) {
copyFileSync(path.join(abiDir, file), path.join(jsonDir, file));
}
}

const index = modules.map((name) => `export * from "./${name}.js";`).join("\n");
writeFileSync(path.join(srcDir, "index.ts"), `${index}\n`);

console.log(`Copied ${modules.length} ABI modules from contracts/abi`);
Loading
Loading