From ad18f39b12468d6549a54ee3be0c96ca68cfc45e Mon Sep 17 00:00:00 2001 From: ayvee Date: Tue, 2 Jun 2026 17:55:14 +0700 Subject: [PATCH 1/2] add agent metadata workflow guidance --- AGENTS.md | 164 ++++++++++++++++++ skills/generate-vault-pair-image/SKILL.md | 62 +++++++ .../scripts/generate_pair_image.mjs | 112 ++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 AGENTS.md create mode 100644 skills/generate-vault-pair-image/SKILL.md create mode 100644 skills/generate-vault-pair-image/scripts/generate_pair_image.mjs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..dac7dcf4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,164 @@ +# AGENTS.md + +Guidance for AI agents working in this repository. + +## Repository Purpose + +This repository contains Berachain metadata used by Berachain interfaces: + +- Token lists in `src/tokens/` +- Reward vault lists in `src/vaults/` +- Validator lists in `src/validators/` +- Image assets in `src/assets/` + +Keep changes scoped to the metadata item being added or fixed. Do not refactor unrelated entries, reformat whole files, or change dependency/workflow files unless the task explicitly requires it. + +## Before Editing + +1. Run `git status -sb` and preserve unrelated local changes. +2. Pull latest `main` before starting a new metadata PR. +3. Verify source-of-truth details from the relevant governance/forum/request link before editing JSON: + - Token name, symbol, decimals, and address + - Reward vault address + - Staking token address + - Protocol/location URL + - Vault name, preferably matching the request unless repo convention clearly requires otherwise + +For reward vault requests, use the forum/request page as the primary source when available. Check the request sections that identify: + +- Reward vault address +- Staking token address +- Token pair and token addresses +- Protocol/pool URL +- Requested vault name +- Any attached protocol, token, or vault images + +If a forum image is low quality, prefer official project brand assets for token images. For generated vault pair images, compose from official token assets and keep the result compliant with the asset rules below. + +## Image Assets + +Assets are committed to `src/assets/**` and uploaded by CI to Cloudflare Images. Do not use Cloudinary for new metadata. + +Current URL format: + +```text +https://imagedelivery.net/qNj7Q3MCke89zoKzav7eDQ///public +``` + +Where `` is one of: + +- `tokens` +- `vaults` +- `validators` +- `protocols` + +Asset requirements: + +- PNG, JPG, or JPEG only +- 1024x1024 pixels +- Under 5 MB +- PNG files must have no transparent pixels +- Use a solid background when converting SVG or transparent source art + +Filename conventions: + +- Token image: `src/assets/tokens/.png` +- Vault image: `src/assets/vaults/.png` +- Validator image: `src/assets/validators/.png` + +For token and vault images, use the exact address casing accepted by `scripts/utils/_imageChecks.ts`. Existing metadata commonly uses lowercase Cloudflare URLs, but the upload script validates non-default asset filenames before upload. + +Do not name a vault image after the staking token address. Current vault asset convention is `src/assets/vaults/.`, and the `logoURI` should use the same vault-address filename. + +## Cloudflare Upload Flow + +New or changed files under `src/assets/**` trigger the `upload-assets` CI job. + +The upload job: + +1. Waits for maintainer approval of the `cloudflare-uploads` GitHub Actions environment. +2. Uploads changed images to Cloudflare Images with IDs like `tokens/` or `vaults/`. +3. Allows the `images` validation check to run against the uploaded URLs. + +Agents should commit the image files and set the expected Cloudflare `logoURI` in JSON before the URL exists. Do not attempt to upload manually unless explicitly asked and credentials are available. + +Some older docs or examples may still mention Cloudinary. Treat those as stale for new metadata unless the repo workflow has changed again. + +## Adding Token Metadata + +Edit `src/tokens/mainnet.json` or the relevant network file. + +Required fields: + +- `chainId` +- `address` +- `symbol` +- `name` +- `decimals` + +Usually include: + +- `logoURI` +- `tags` when appropriate, for example `["stablecoin"]` +- `extensions.coingeckoId`, `extensions.pythPriceId`, or `extensions.beraPythPriceId` only when verified + +## Adding Vault Metadata + +Edit `src/vaults/mainnet.json` or the relevant network file. + +Required fields: + +- `stakingTokenAddress` +- `vaultAddress` +- `name` +- `protocol` +- `url` +- `categories` + +Usually include: + +- `logoURI` +- `description` +- `action` +- `owner` when it is already part of the local convention for that protocol/project + +Vaults should only be added when they are whitelisted/passed through the relevant governance or forum process. Link the source in the PR body. + +Use the forum/request vault name unless there is a clear, verified reason to adapt it to an existing naming convention. Do not add details such as fee tier, island type, or owner unless they are verified from the request, Hub, Kodiak, or another source of truth. + +For Kodiak vaults, common fields are: + +- `protocol`: `Kodiak` +- `categories`: `["defi/amm"]` +- `url`: Kodiak pool URL, including `farm=` when available +- `description`: `Acquired by depositing liquidity into the Pool on Kodiak` +- `action`: `Stake / ` + +## Validation + +Run focused checks after editing: + +```bash +pnpm biome check +pnpm validate:json . +pnpm validate:images . +``` + +`pnpm validate` also runs data, Pyth, and CoinGecko checks. Those may require network access or CI secrets, so local failures can be environmental. Record exactly what was run and any environmental limitation in the PR body. + +If `tsx` fails locally with an IPC permission error under the system temp directory, rerun the same validator with appropriate sandbox approval instead of changing code. + +## PR Expectations + +PRs should include: + +- A concise summary of metadata/assets added +- The governance/forum/source link used for verification +- The key addresses verified from that source +- Validation commands run +- Note that new assets require `cloudflare-uploads` approval if `src/assets/**` changed + +Stage only the intended files. For asset PRs, this is usually: + +- The relevant JSON metadata file(s) +- The corresponding file(s) under `src/assets/**` diff --git a/skills/generate-vault-pair-image/SKILL.md b/skills/generate-vault-pair-image/SKILL.md new file mode 100644 index 00000000..d003ffe3 --- /dev/null +++ b/skills/generate-vault-pair-image/SKILL.md @@ -0,0 +1,62 @@ +--- +name: generate-vault-pair-image +description: Generate repo-ready split-token vault, pool, or LP images from two underlying token logos. Use when a user needs a 1024x1024 paired-token vault image, half-and-half token icon, LP pair logo, Berachain metadata vault asset, Cloudflare-ready token pair image, or asks to recreate a blurry forum vault image from cleaner underlying token assets. +--- + +# Generate Vault Pair Image + +## Workflow + +1. Identify the two underlying token images. + - Prefer official brand-kit assets or existing repository token assets. + - If the source is SVG or transparent PNG, render/flatten it onto a solid background. + - Do not upscale a blurry forum image when cleaner token assets exist. +2. Decide output path and naming. + - For Berachain metadata vaults, use `src/assets/vaults/.png`. + - Do not name vault images after the staking token/pool address. +3. Generate a 1024x1024 image. + - Left token fills the left half. + - Right token fills the right half. + - Apply an outer circle border and center divider. + - Flatten onto a solid background so PNG transparency checks pass. +4. Validate: + - Dimensions must be `1024x1024`. + - PNG must have no transparent pixels. + - File should be under 5 MB. + +## Script + +Use `scripts/generate_pair_image.mjs` for deterministic generation. It uses this repo's `sharp` dependency, so run it from the repository root. + +Example: + +```bash +node skills/generate-vault-pair-image/scripts/generate_pair_image.mjs \ + --left src/assets/tokens/0xbca138DEd469F5063589bFdfdD4BC68EB1c3f252.png \ + --right src/assets/tokens/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce.png \ + --out src/assets/vaults/0x181f3b1d55299f3744188f7ecd082c75a97d2d4f.png \ + --border '#ffffff' \ + --background '#ffffff' +``` + +Common options: + +- `--left`: left token image, including SVG/PNG/JPG/WebP supported by `sharp` +- `--right`: right token image +- `--out`: output PNG path +- `--border`: outer circle and center divider color, default `#ffffff` +- `--background`: flattened background color, default `#ffffff` +- `--border-width`: outer border width, default `52` +- `--divider-width`: center divider width, default `46` +- `--fit`: token resize mode, default `cover`; use `contain` for logos that should not crop + +## Berachain Metadata Notes + +For metadata PRs: + +- Set `logoURI` to the expected Cloudflare Images URL before CI uploads it. +- Use `https://imagedelivery.net/qNj7Q3MCke89zoKzav7eDQ/vaults/.png/public`. +- Commit the generated image under `src/assets/vaults/`. +- `pnpm validate:images .` should pass after the JSON entry references the image. + +If the pair image is based on a forum/request asset, verify the request name and addresses from the forum, but generate the image from official token assets when the attached image is low quality. diff --git a/skills/generate-vault-pair-image/scripts/generate_pair_image.mjs b/skills/generate-vault-pair-image/scripts/generate_pair_image.mjs new file mode 100644 index 00000000..feb3774d --- /dev/null +++ b/skills/generate-vault-pair-image/scripts/generate_pair_image.mjs @@ -0,0 +1,112 @@ +#!/usr/bin/env node +import fs from "node:fs"; +import path from "node:path"; +import { createRequire } from "node:module"; + +const requireFromCwd = createRequire(path.join(process.cwd(), "package.json")); +const sharp = requireFromCwd("sharp"); + +const args = new Map(); +for (let i = 2; i < process.argv.length; i += 1) { + const arg = process.argv[i]; + if (!arg.startsWith("--")) continue; + const key = arg.slice(2); + const next = process.argv[i + 1]; + if (!next || next.startsWith("--")) { + args.set(key, "true"); + } else { + args.set(key, next); + i += 1; + } +} + +const required = ["left", "right", "out"]; +for (const key of required) { + if (!args.get(key)) { + console.error(`Missing --${key}`); + process.exit(1); + } +} + +const size = Number(args.get("size") ?? 1024); +const border = args.get("border") ?? "#ffffff"; +const background = args.get("background") ?? "#ffffff"; +const borderWidth = Number(args.get("border-width") ?? 52); +const dividerWidth = Number(args.get("divider-width") ?? 46); +const fit = args.get("fit") ?? "cover"; + +const leftPath = args.get("left"); +const rightPath = args.get("right"); +const outPath = args.get("out"); + +for (const input of [leftPath, rightPath]) { + if (!fs.existsSync(input)) { + console.error(`Input not found: ${input}`); + process.exit(1); + } +} + +if (!["cover", "contain", "fill", "inside", "outside"].includes(fit)) { + console.error(`Invalid --fit value: ${fit}`); + process.exit(1); +} + +const renderToken = async (input) => + sharp(input, { density: 2048 }) + .resize(size, size, { fit, background }) + .flatten({ background }) + .png() + .toBuffer(); + +const halfMask = (side) => { + const x = side === "left" ? 0 : size / 2; + return Buffer.from( + ``, + ); +}; + +const borderSvg = Buffer.from(` + + + +`); + +const main = async () => { + const left = await sharp(await renderToken(leftPath)) + .composite([{ input: halfMask("left"), blend: "dest-in" }]) + .png() + .toBuffer(); + const right = await sharp(await renderToken(rightPath)) + .composite([{ input: halfMask("right"), blend: "dest-in" }]) + .png() + .toBuffer(); + + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + + await sharp({ + create: { + width: size, + height: size, + channels: 4, + background, + }, + }) + .composite([ + { input: left, left: 0, top: 0 }, + { input: right, left: 0, top: 0 }, + { input: borderSvg, left: 0, top: 0 }, + ]) + .flatten({ background }) + .png() + .toFile(outPath); + + const meta = await sharp(outPath).metadata(); + const stat = fs.statSync(outPath); + console.log(`${outPath}`); + console.log(`${meta.width}x${meta.height}, ${stat.size} bytes`); +}; + +main().catch((err) => { + console.error(err); + process.exit(1); +}); From 95b0f193ea18ad8cfe03b4f0d8cbda101ccf75bb Mon Sep 17 00:00:00 2001 From: ayvee Date: Tue, 2 Jun 2026 17:58:34 +0700 Subject: [PATCH 2/2] fix vault pair skill formatting --- .../generate-vault-pair-image/scripts/generate_pair_image.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/generate-vault-pair-image/scripts/generate_pair_image.mjs b/skills/generate-vault-pair-image/scripts/generate_pair_image.mjs index feb3774d..33bc1deb 100644 --- a/skills/generate-vault-pair-image/scripts/generate_pair_image.mjs +++ b/skills/generate-vault-pair-image/scripts/generate_pair_image.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node import fs from "node:fs"; -import path from "node:path"; import { createRequire } from "node:module"; +import path from "node:path"; const requireFromCwd = createRequire(path.join(process.cwd(), "package.json")); const sharp = requireFromCwd("sharp");