diff --git a/.github/workflows/check-development-to-clients.yml b/.github/workflows/check-development-to-clients.yml deleted file mode 100644 index b2a359b..0000000 --- a/.github/workflows/check-development-to-clients.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Enforce Merge Policy - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - check-source-branch: - runs-on: ubuntu-latest - steps: - - name: Validate source branch - run: | - SOURCE_BRANCH="${{ github.head_ref }}" - TARGET_BRANCH="${{ github.base_ref }}" - - PROTECTED_BRANCHES="ldo crv aero uni" - ALLOWED_SOURCE="development" - - if echo "$PROTECTED_BRANCHES" | grep -qw "$TARGET_BRANCH"; then - if [ "$SOURCE_BRANCH" != "$ALLOWED_SOURCE" ]; then - echo "::error::Merge denied. Branch '$TARGET_BRANCH' only accepts merges from '$ALLOWED_SOURCE'." - exit 1 - fi - fi - - echo "βœ… Check passed" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9bff6f3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + pull_request: + push: + branches: [development, main] + +permissions: + contents: read + +jobs: + checks: + runs-on: ubuntu-latest + steps: + # Third-party actions pinned to full commit SHAs (supply-chain policy) + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + + - run: pnpm install --frozen-lockfile + + - name: Vendored schema integrity + run: node scripts/check-schema-drift.mjs + + - name: Lint and format + run: pnpm exec biome check src tests + + - name: Type check + run: pnpm type-check + + - name: Tests + run: pnpm test + + - name: Build + run: pnpm build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..8eb3083 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,196 @@ +name: Deploy + +# Self-contained Vercel deploy β€” no Vercel git-integration, so no contributor +# needs Vercel membership. The Action deploys on behalf of one VERCEL_TOKEN, +# building the app and uploading the prebuilt output via the Vercel CLI. +# +# Ways in: +# - workflow_dispatch: manual or cross-repo (otf-cms triggers a content +# preview, or a teardown when the content PR closes). +# - push to main: production deploy of the app's own code. Production content +# arrives at runtime from the published Release. +# +# When a content preview is requested with comment_repo/comment_pr, it posts a +# single status comment on that PR and updates it through the lifecycle +# (generating β†’ ready/failed), mimicking a native Vercel PR comment. On +# action=teardown it removes that PR's preview deployment. +# +# Required repo secrets (your fork for testing, aragon/* for real): +# VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID Vercel CLI auth + target +# OTF_CONTENT_TOKEN read otf-cms for build-from-ref +# CMS_COMMENT_TOKEN comment on the content PR +# Optional repo variable: +# OTF_CONTENT_REPO defaults to aragon/otf-cms; set to /otf-cms on a fork + +on: + workflow_dispatch: + inputs: + action: + description: deploy or teardown + type: choice + options: [deploy, teardown] + default: deploy + environment: + description: Vercel target + type: choice + options: [preview, production] + default: preview + content_ref: + description: otf-cms ref for a build-from-ref preview (blank = committed/runtime data) + type: string + default: "" + comment_repo: + description: "owner/repo of a PR to post status to (optional, e.g. the otf-cms content PR)" + type: string + default: "" + comment_pr: + description: PR number in comment_repo to post status to (optional) + type: string + default: "" + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: deploy-${{ github.event.inputs.comment_pr || github.event.inputs.environment || github.ref }} + cancel-in-progress: true + +jobs: + deploy: + if: ${{ github.event.inputs.action != 'teardown' }} + runs-on: ubuntu-latest + # The Vercel CLI reads these from the environment (equivalent to --token / + # project linking), so nothing sensitive is ever inlined into a run script. + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + TARGET: ${{ github.event.inputs.environment || 'production' }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + # Third-party actions pinned to full commit SHAs (supply-chain policy) + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + + - run: pnpm install --frozen-lockfile + + # Status comment: post "generating" up front so the editor sees the build + # is underway (native-Vercel-bot feel). Captures the comment id to update. + - name: Open status comment + id: comment + if: ${{ github.event.inputs.comment_pr != '' }} + env: + GH_TOKEN: ${{ secrets.CMS_COMMENT_TOKEN }} + COMMENT_REPO: ${{ github.event.inputs.comment_repo }} + COMMENT_PR: ${{ github.event.inputs.comment_pr }} + CONTENT_REF: ${{ github.event.inputs.content_ref }} + run: | + printf -v body '### πŸ”„ Preview β€” building…\nFrom content branch `%s`. ([build logs](%s))' \ + "${CONTENT_REF:-this content}" "$RUN_URL" + id=$(gh api "repos/$COMMENT_REPO/issues/$COMMENT_PR/comments" -f body="$body" --jq '.id') + echo "id=$id" >> "$GITHUB_OUTPUT" + + # Gate: never ship code that doesn't type-check or pass tests. + - run: pnpm type-check + - run: pnpm test + + # Pin a specific version once confirmed on the fork; latest is fine while + # experimenting. + - name: Install Vercel CLI + run: pnpm add -g vercel@latest + + - name: Pull Vercel project config + run: vercel pull --yes --environment="$TARGET" + + # build-data reads OTF_CONTENT_REF: set β†’ build-from-ref preview; blank β†’ + # committed data (production, where content comes from the Release). + - name: Build + env: + OTF_CONTENT_REF: ${{ github.event.inputs.content_ref }} + OTF_CONTENT_TOKEN: ${{ secrets.OTF_CONTENT_TOKEN }} + OTF_CONTENT_REPO: ${{ vars.OTF_CONTENT_REPO }} + run: | + if [ "$TARGET" = production ]; then + vercel build --prod + else + vercel build + fi + + - name: Deploy prebuilt output + id: deploy + # --yes confirms default settings for a never-deployed project (sets + # skipAutoDetectionConfirmation) so a fresh project deploys non-interactively. + run: | + if [ "$TARGET" = production ]; then + url=$(vercel deploy --prebuilt --prod --yes) + else + url=$(vercel deploy --prebuilt --yes) + fi + echo "url=$url" >> "$GITHUB_OUTPUT" + echo "Deployed ($TARGET): $url" >> "$GITHUB_STEP_SUMMARY" + + # Finalize the status comment β€” runs even if the build/deploy failed, so a + # broken preview is surfaced on the PR instead of failing silently. + - name: Finalize status comment + if: ${{ always() && github.event.inputs.comment_pr != '' }} + env: + GH_TOKEN: ${{ secrets.CMS_COMMENT_TOKEN }} + COMMENT_REPO: ${{ github.event.inputs.comment_repo }} + COMMENT_PR: ${{ github.event.inputs.comment_pr }} + COMMENT_ID: ${{ steps.comment.outputs.id }} + URL: ${{ steps.deploy.outputs.url }} + CONTENT_REF: ${{ github.event.inputs.content_ref }} + DEPLOY_OK: ${{ steps.deploy.outcome == 'success' }} + run: | + if [ "$DEPLOY_OK" = "true" ] && [ -n "$URL" ]; then + printf -v body '### βœ… Preview ready\nContent branch `%s`.\n\n**πŸ‘‰ [Open the preview](%s)**' \ + "${CONTENT_REF:-this content}" "$URL" + else + printf -v body '### ❌ Preview build failed\nContent branch `%s`. [See logs](%s)' \ + "${CONTENT_REF:-this content}" "$RUN_URL" + fi + if [ -n "$COMMENT_ID" ]; then + gh api -X PATCH "repos/$COMMENT_REPO/issues/comments/$COMMENT_ID" -f body="$body" + else + gh api "repos/$COMMENT_REPO/issues/$COMMENT_PR/comments" -f body="$body" + fi + + teardown: + if: ${{ github.event.inputs.action == 'teardown' }} + runs-on: ubuntu-latest + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + steps: + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + + - name: Install Vercel CLI + run: npm i -g vercel@latest + + # Find the preview URL we posted on the (now-closed) content PR and remove + # that deployment, then note it on the PR. + - name: Remove preview deployment + env: + GH_TOKEN: ${{ secrets.CMS_COMMENT_TOKEN }} + COMMENT_REPO: ${{ github.event.inputs.comment_repo }} + COMMENT_PR: ${{ github.event.inputs.comment_pr }} + run: | + host=$(gh api "repos/$COMMENT_REPO/issues/$COMMENT_PR/comments" \ + --jq '[.[].body | capture("https://(?[a-z0-9.-]+\\.vercel\\.app)")? | .h] | map(select(. != null)) | last // empty') + if [ -n "$host" ]; then + vercel rm "$host" --yes || true + gh api "repos/$COMMENT_REPO/issues/$COMMENT_PR/comments" \ + -f body="πŸ—‘οΈ Preview torn down (content PR closed)." + else + echo "No preview URL found on PR #$COMMENT_PR β€” nothing to remove." + fi diff --git a/.github/workflows/update-token-timestamps.yml b/.github/workflows/update-token-timestamps.yml deleted file mode 100644 index 0bd701b..0000000 --- a/.github/workflows/update-token-timestamps.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Update token timestamps - -on: - push: - branches: [development] - paths: - - src/data/metrics.json - - src/data/tokens.json - -permissions: - contents: write - -jobs: - update-token-timestamp: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - name: Load secrets - id: load-secrets - uses: 1password/load-secrets-action@8d0d610af187e78a2772c2d18d627f4c52d3fbfb # v3.1.0 - with: - export-env: false - env: - OP_SERVICE_ACCOUNT_TOKEN: "${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}" - GPG_PASSPHRASE: op://kv_ownership-token-index_infra/arabot-1_SIGN_CERTS/credential - GPG_PRIVATE_KEY: op://kv_ownership-token-index_infra/arabot-1_SIGN_CERTS/private_key - - - name: Checkout development - uses: actions/checkout@v4 - with: - ref: development - fetch-depth: 0 - - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 - with: - gpg_private_key: ${{ steps.load-secrets.outputs.GPG_PRIVATE_KEY }} - passphrase: ${{ steps.load-secrets.outputs.GPG_PASSPHRASE }} - git_user_signingkey: true - git_commit_gpgsign: true - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Detect changed token ids - id: changed - run: | - ids=$(node scripts/get-changed-token-ids.mjs --before "${{ github.event.before }}" --after "${{ github.event.after }}") - echo "ids=$ids" >> $GITHUB_OUTPUT - - - name: Resolve commit timestamp - id: timestamp - run: | - echo "value=$(node -e 'console.log(Math.floor(new Date(process.argv[1]).getTime() / 1000))' '${{ github.event.head_commit.timestamp }}')" >> $GITHUB_OUTPUT - - - name: Update timestamp - if: steps.changed.outputs.ids != '' - run: node scripts/update-token-timestamps.mjs --ids "${{ steps.changed.outputs.ids }}" --timestamp "${{ steps.timestamp.outputs.value }}" - - - name: Set commit message - id: commit-message - run: | - echo "message=chore: update token lastUpdated" >> $GITHUB_OUTPUT - - - name: Commit and push - run: | - if git diff --quiet; then - echo "No changes to commit." - exit 0 - fi - git add src/data/tokens.json - git commit -am "${{ steps.commit-message.outputs.message }}" - git push origin HEAD:development diff --git a/.gitignore b/.gitignore index 3dff5d5..b1cff0b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ output .agents .tempor .playwright-mcp +.env.otf-app-secrets +.pnpm-store diff --git a/biome.json b/biome.json index 5923c75..f76376b 100644 --- a/biome.json +++ b/biome.json @@ -13,7 +13,8 @@ "**/index.html", "**/vite.config.js", "!**/src/routeTree.gen.ts", - "!**/src/styles.css" + "!**/src/styles.css", + "!**/src/data/generated" ] }, "formatter": { diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..4262f0a --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,28 @@ +# Local rails β€” same gates as CI (.github/workflows/ci.yml), run on your +# machine so a red build is caught before it leaves it. +# +# pre-commit fast: format/lint only the staged files, auto-fix and re-stage +# pre-push full: the exact checks CI runs (build stays CI-only β€” too heavy +# for every push) +# +# Skip in a pinch with `git commit --no-verify` / `git push --no-verify`. + +pre-commit: + parallel: true + commands: + biome: + glob: "*.{ts,tsx,js,jsx,json,jsonc,css}" + run: pnpm exec biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files} + stage_fixed: true + +pre-push: + parallel: true + commands: + schema-drift: + run: node scripts/check-schema-drift.mjs + biome: + run: pnpm exec biome check src tests + type-check: + run: pnpm type-check + test: + run: pnpm test diff --git a/package.json b/package.json index 40ebe6a..a28f27a 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,17 @@ "private": true, "type": "module", "scripts": { - "dev": "node scripts/sync-coingecko-ids.mjs --write && vite dev --port 3000", - "build": "node scripts/sync-coingecko-ids.mjs --write && vite build", + "dev": "vite dev --port 3000", + "build": "tsx scripts/build-data.ts && vite build", + "build:data": "tsx scripts/build-data.ts", "preview": "vite preview", "test": "vitest run", "format": "biome format", "lint": "biome lint", "check": "biome check --write", "type-check": "tsc --noEmit", - "og:generate": "node scripts/generate-og-screenshots.mjs" + "og:generate": "node scripts/generate-og-screenshots.mjs", + "prepare": "lefthook install" }, "dependencies": { "@base-ui/react": "^1.2.0", @@ -61,12 +63,14 @@ "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.4", "jsdom": "^27.4.0", + "lefthook": "2.1.9", "playwright": "^1.58.2", "shadcn": "^3.8.5", "sharp": "^0.34.5", + "tsx": "^4.22.4", "typescript": "^5.9.3", "vite": "^7.3.1", - "vitest": "^3.2.4", + "vitest": "4.1.7", "web-vitals": "^5.1.0" }, "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6f7bce..07b04ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,7 +37,7 @@ importers: version: 0.5.19(tailwindcss@4.2.0) '@tailwindcss/vite': specifier: ^4.2.0 - version: 4.2.0(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 4.2.0(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) '@tanstack/react-devtools': specifier: ^0.7.11 version: 0.7.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.10) @@ -55,13 +55,13 @@ importers: version: 1.161.3(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.4))(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.161.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start': specifier: ^1.161.3 - version: 1.161.3(crossws@0.4.4(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 1.161.3(crossws@0.4.4(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-plugin': specifier: ^1.161.3 - version: 1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) '@unpic/react': specifier: ^1.0.2 version: 1.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -76,7 +76,7 @@ importers: version: 0.561.0(react@19.2.4) nitro: specifier: 3.0.1-alpha.2 - version: 3.0.1-alpha.2(lru-cache@11.2.6)(rollup@4.57.1)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 3.0.1-alpha.2(lru-cache@11.2.6)(rollup@4.57.1)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) react: specifier: ^19.2.4 version: 19.2.4 @@ -112,7 +112,7 @@ importers: version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) zod: specifier: ^4.3.6 version: 4.3.6 @@ -122,7 +122,7 @@ importers: version: 2.4.3 '@tanstack/devtools-vite': specifier: ^0.3.12 - version: 0.3.12(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 0.3.12(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 @@ -140,10 +140,13 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^5.1.4 - version: 5.1.4(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + version: 5.1.4(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) jsdom: specifier: ^27.4.0 version: 27.4.0(@noble/hashes@1.8.0) + lefthook: + specifier: 2.1.9 + version: 2.1.9 playwright: specifier: ^1.58.2 version: 1.58.2 @@ -153,15 +156,18 @@ importers: sharp: specifier: ^0.34.5 version: 0.34.5 + tsx: + specifier: ^4.22.4 + version: 4.22.4 typescript: specifier: ^5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + version: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@1.8.0))(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(tsx@4.21.0) + specifier: 4.1.7 + version: 4.1.7(@types/node@22.19.11)(jsdom@27.4.0(@noble/hashes@1.8.0))(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) web-vitals: specifier: ^5.1.0 version: 5.1.0 @@ -495,156 +501,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.27.3': resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.27.3': resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.27.3': resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.27.3': resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.27.3': resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.27.3': resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.3': resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.27.3': resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.27.3': resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.27.3': resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.27.3': resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.27.3': resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.27.3': resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.27.3': resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.27.3': resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.27.3': resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.27.3': resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.3': resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.27.3': resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.3': resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.27.3': resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.27.3': resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.27.3': resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.27.3': resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.27.3': resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@exodus/bytes@1.14.1': resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1559,6 +1721,9 @@ packages: peerDependencies: solid-js: ^1.6.12 + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -2011,34 +2176,34 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.7': + resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + '@vitest/mocker@4.1.7': + resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.7': + resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.1.7': + resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.1.7': + resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.7': + resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.7': + resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} @@ -2156,10 +2321,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2178,8 +2339,8 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} chalk@5.6.2: @@ -2198,10 +2359,6 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - check-error@2.1.3: - resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} - engines: {node: '>= 16'} - cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -2436,10 +2593,6 @@ packages: babel-plugin-macros: optional: true - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -2559,8 +2712,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -2571,6 +2724,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2749,9 +2907,6 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2985,9 +3140,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -3034,6 +3186,60 @@ packages: launch-editor@2.13.0: resolution: {integrity: sha512-u+9asUHMJ99lA15VRMXw5XKfySFR9dGXwgsgS14YTbUq3GITP58mIM32At90P5fZ+MUId5Yw+IwI/yKub7jnCQ==} + lefthook-darwin-arm64@2.1.9: + resolution: {integrity: sha512-119HryNcvr4nqn0wUIrNPgpMEPn9yMQzEcW/lezRsnb56PCJriJB92+MCySPVcWDxJnZef7o0T3jdnPNiSH7Qg==} + cpu: [arm64] + os: [darwin] + + lefthook-darwin-x64@2.1.9: + resolution: {integrity: sha512-dwo5Tke2XcQCM56DGHgFKBfRbJIL6xs2wZ0zG1TUVZgl4t4mQUt6LiZ4V/ZQfYHTZF9qywvXoIlR5N35qOaiVQ==} + cpu: [x64] + os: [darwin] + + lefthook-freebsd-arm64@2.1.9: + resolution: {integrity: sha512-+09PVap6nl6xsaHch5JLtq7WvIR++U1Q2MzA2ai0M4uB/VP3AqrvKqHw6+9hjyKnIH+HHL83uqi77EAY+LaxLA==} + cpu: [arm64] + os: [freebsd] + + lefthook-freebsd-x64@2.1.9: + resolution: {integrity: sha512-8XresjKIYpkE9ARgCtBEZgJZxAU3T4MIqzj4zNy15XRT59I1Us+QdqXTNm+pkZ41Yd2X/nxs2Pkvbq3NWWlIGw==} + cpu: [x64] + os: [freebsd] + + lefthook-linux-arm64@2.1.9: + resolution: {integrity: sha512-1oNIQfwrPe6rgU2KcDM3aF6+hpZDCKx1TmawQKpXUY5gVsbZ7MqX0Sk/1lnnWxqPm+kQQ5f6J2dpFWd+4xH8jg==} + cpu: [arm64] + os: [linux] + + lefthook-linux-x64@2.1.9: + resolution: {integrity: sha512-fT+7Q+BJyGp+CslFQkNXmdFRgyVXsPHPi9NAsDX0a6QOyNnoORByAsvx6zeAKuF5rL3BBgNfho1/v2RuGxGy9w==} + cpu: [x64] + os: [linux] + + lefthook-openbsd-arm64@2.1.9: + resolution: {integrity: sha512-4bVuafBk3dddVNo0+3hMbjcJs4mqYAstxpPMmX2ufkudSTYFNIhWoqwuGVQV/SS/xdcOKJAldW4qayAzed2ysw==} + cpu: [arm64] + os: [openbsd] + + lefthook-openbsd-x64@2.1.9: + resolution: {integrity: sha512-PmPoMmLP/wQQWcQ9u2YH86bTZ3UCfBsxuEmVTEyPU2U8R1qSTp5r/Gs3G8cN5Mxo91XB9oBERtF1n+xD3W6aVA==} + cpu: [x64] + os: [openbsd] + + lefthook-windows-arm64@2.1.9: + resolution: {integrity: sha512-KphfkBKmwBnmolyrdhIl3lrBaOyTcCgXBT2AB/9OHnEXhOLvv5uTCUkrD4YRAxXPtFKq6UvnapIeoL3GZq0bdA==} + cpu: [arm64] + os: [win32] + + lefthook-windows-x64@2.1.9: + resolution: {integrity: sha512-2qlUtkJHZ3MyUxgV5XTEmcrIoNZA07iwaquoswAcqv/1MeBFXlD+O+koFRfrzWng2O5WYEbpJnd8tvaYnV8fTA==} + cpu: [x64] + os: [win32] + + lefthook@2.1.9: + resolution: {integrity: sha512-bwDaIOViTktE8kJLf9jP0p+H2/RDTlFFlc43Am2YgUsX22hI6Sq4RbzsrecwzY5y+MHTipOH7WsmWSEniePHWQ==} + hasBin: true + lightningcss-android-arm64@1.31.1: resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} engines: {node: '>= 12.0.0'} @@ -3125,9 +3331,6 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lru-cache@11.2.6: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} @@ -3376,6 +3579,10 @@ packages: resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} engines: {node: '>= 10'} + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} + ofetch@2.0.0-alpha.3: resolution: {integrity: sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA==} @@ -3470,10 +3677,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3679,9 +3882,6 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -3846,8 +4046,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} @@ -3891,9 +4091,6 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-literal@3.1.0: - resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -3929,9 +4126,6 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -3940,16 +4134,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} tldts-core@7.0.23: @@ -4004,8 +4190,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + tsx@4.22.4: + resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} engines: {node: '>=18.0.0'} hasBin: true @@ -4209,11 +4395,6 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -4270,26 +4451,39 @@ packages: vite: optional: true - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@4.1.7: + resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.7 + '@vitest/browser-preview': 4.1.7 + '@vitest/browser-webdriverio': 4.1.7 + '@vitest/coverage-istanbul': 4.1.7 + '@vitest/coverage-v8': 4.1.7 + '@vitest/ui': 4.1.7 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': optional: true '@vitest/ui': optional: true @@ -4804,81 +4998,159 @@ snapshots: '@esbuild/aix-ppc64@0.27.3': optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/android-arm64@0.27.3': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm@0.27.3': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-x64@0.27.3': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.27.3': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.27.3': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.27.3': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.27.3': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.27.3': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm@0.27.3': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-ia32@0.27.3': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-loong64@0.27.3': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.27.3': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.27.3': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.27.3': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-s390x@0.27.3': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-x64@0.27.3': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + '@esbuild/netbsd-arm64@0.27.3': optional: true + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.27.3': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.27.3': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.27.3': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + '@esbuild/openharmony-arm64@0.27.3': optional: true + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.27.3': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.27.3': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-ia32@0.27.3': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-x64@0.27.3': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@exodus/bytes@1.14.1(@noble/hashes@1.8.0)': optionalDependencies: '@noble/hashes': 1.8.0 @@ -5522,6 +5794,8 @@ snapshots: dependencies: solid-js: 1.9.10 + '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} '@tabler/icons-react@3.37.1(react@19.2.4)': @@ -5597,12 +5871,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.0 - '@tailwindcss/vite@4.2.0(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@tailwindcss/vite@4.2.0(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4))': dependencies: '@tailwindcss/node': 4.2.0 '@tailwindcss/oxide': 4.2.0 tailwindcss: 4.2.0 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) '@tanstack/devtools-client@0.0.3': dependencies: @@ -5631,7 +5905,7 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools-vite@0.3.12(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@tanstack/devtools-vite@0.3.12(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4))': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -5643,7 +5917,7 @@ snapshots: chalk: 5.6.2 launch-editor: 2.13.0 picomatch: 4.0.3 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) transitivePeerDependencies: - bufferutil - supports-color @@ -5742,19 +6016,19 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/react-start@1.161.3(crossws@0.4.4(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@tanstack/react-start@1.161.3(crossws@0.4.4(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4))': dependencies: '@tanstack/react-router': 1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start-client': 1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start-server': 1.161.3(crossws@0.4.4(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-utils': 1.158.0 '@tanstack/start-client-core': 1.161.3 - '@tanstack/start-plugin-core': 1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.10.1))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@tanstack/start-plugin-core': 1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.10.1))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) '@tanstack/start-server-core': 1.161.3(crossws@0.4.4(srvx@0.10.1)) pathe: 2.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) transitivePeerDependencies: - '@rsbuild/core' - crossws @@ -5802,12 +6076,12 @@ snapshots: prettier: 3.8.1 recast: 0.23.11 source-map: 0.7.6 - tsx: 4.21.0 + tsx: 4.22.4 zod: 3.25.76 transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@tanstack/router-plugin@1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -5824,7 +6098,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) transitivePeerDependencies: - supports-color @@ -5858,7 +6132,7 @@ snapshots: '@tanstack/start-fn-stubs@1.154.7': {} - '@tanstack/start-plugin-core@1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.10.1))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@tanstack/start-plugin-core@1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.4(srvx@0.10.1))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4))': dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.29.0 @@ -5866,7 +6140,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.40 '@tanstack/router-core': 1.161.3 '@tanstack/router-generator': 1.161.3 - '@tanstack/router-plugin': 1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + '@tanstack/router-plugin': 1.161.3(@tanstack/react-router@1.161.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) '@tanstack/router-utils': 1.158.0 '@tanstack/start-client-core': 1.161.3 '@tanstack/start-server-core': 1.161.3(crossws@0.4.4(srvx@0.10.1)) @@ -5878,8 +6152,8 @@ snapshots: srvx: 0.11.7 tinyglobby: 0.2.15 ufo: 1.6.3 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) - vitefu: 1.1.1(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) + vitefu: 1.1.1(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) xmlbuilder2: 4.0.3 zod: 3.25.76 transitivePeerDependencies: @@ -6050,7 +6324,7 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -6058,52 +6332,51 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) transitivePeerDependencies: - supports-color - '@vitest/expect@3.2.4': + '@vitest/expect@4.1.7': dependencies: + '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - tinyrainbow: 2.0.0 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))': + '@vitest/mocker@4.1.7(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4))': dependencies: - '@vitest/spy': 3.2.4 + '@vitest/spy': 4.1.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.10(@types/node@22.19.11)(typescript@5.9.3) - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) - '@vitest/pretty-format@3.2.4': + '@vitest/pretty-format@4.1.7': dependencies: - tinyrainbow: 2.0.0 + tinyrainbow: 3.1.0 - '@vitest/runner@3.2.4': + '@vitest/runner@4.1.7': dependencies: - '@vitest/utils': 3.2.4 + '@vitest/utils': 4.1.7 pathe: 2.0.3 - strip-literal: 3.1.0 - '@vitest/snapshot@3.2.4': + '@vitest/snapshot@4.1.7': dependencies: - '@vitest/pretty-format': 3.2.4 + '@vitest/pretty-format': 4.1.7 + '@vitest/utils': 4.1.7 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.4 + '@vitest/spy@4.1.7': {} - '@vitest/utils@3.2.4': + '@vitest/utils@4.1.7': dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 + '@vitest/pretty-format': 4.1.7 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 accepts@2.0.0: dependencies: @@ -6217,8 +6490,6 @@ snapshots: bytes@3.1.2: {} - cac@6.7.14: {} - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -6235,13 +6506,7 @@ snapshots: ccount@2.0.1: {} - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.3 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 + chai@6.2.2: {} chalk@5.6.2: {} @@ -6253,8 +6518,6 @@ snapshots: character-reference-invalid@2.0.1: {} - check-error@2.1.3: {} - cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -6451,8 +6714,6 @@ snapshots: dedent@1.7.1: {} - deep-eql@5.0.2: {} - deepmerge@4.3.1: {} default-browser-id@5.0.1: {} @@ -6554,7 +6815,7 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} + es-module-lexer@2.1.0: {} es-object-atoms@1.1.1: dependencies: @@ -6589,6 +6850,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -6791,10 +7081,6 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - get-tsconfig@4.13.6: - dependencies: - resolve-pkg-maps: 1.0.0 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -6991,8 +7277,6 @@ snapshots: js-tokens@4.0.0: {} - js-tokens@9.0.1: {} - js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -7050,6 +7334,49 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.8.3 + lefthook-darwin-arm64@2.1.9: + optional: true + + lefthook-darwin-x64@2.1.9: + optional: true + + lefthook-freebsd-arm64@2.1.9: + optional: true + + lefthook-freebsd-x64@2.1.9: + optional: true + + lefthook-linux-arm64@2.1.9: + optional: true + + lefthook-linux-x64@2.1.9: + optional: true + + lefthook-openbsd-arm64@2.1.9: + optional: true + + lefthook-openbsd-x64@2.1.9: + optional: true + + lefthook-windows-arm64@2.1.9: + optional: true + + lefthook-windows-x64@2.1.9: + optional: true + + lefthook@2.1.9: + optionalDependencies: + lefthook-darwin-arm64: 2.1.9 + lefthook-darwin-x64: 2.1.9 + lefthook-freebsd-arm64: 2.1.9 + lefthook-freebsd-x64: 2.1.9 + lefthook-linux-arm64: 2.1.9 + lefthook-linux-x64: 2.1.9 + lefthook-openbsd-arm64: 2.1.9 + lefthook-openbsd-x64: 2.1.9 + lefthook-windows-arm64: 2.1.9 + lefthook-windows-x64: 2.1.9 + lightningcss-android-arm64@1.31.1: optional: true @@ -7114,8 +7441,6 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@3.2.1: {} - lru-cache@11.2.6: {} lru-cache@5.1.1: @@ -7434,7 +7759,7 @@ snapshots: nf3@0.3.10: {} - nitro@3.0.1-alpha.2(lru-cache@11.2.6)(rollup@4.57.1)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): + nitro@3.0.1-alpha.2(lru-cache@11.2.6)(rollup@4.57.1)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)): dependencies: consola: 3.4.2 crossws: 0.4.4(srvx@0.10.1) @@ -7452,7 +7777,7 @@ snapshots: unstorage: 2.0.0-alpha.5(db0@0.3.4)(lru-cache@11.2.6)(ofetch@2.0.0-alpha.3) optionalDependencies: rollup: 4.57.1 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -7513,6 +7838,8 @@ snapshots: object-treeify@1.1.33: {} + obug@2.1.3: {} + ofetch@2.0.0-alpha.3: {} ohash@2.0.11: {} @@ -7658,8 +7985,6 @@ snapshots: pathe@2.0.3: {} - pathval@2.0.1: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -7882,8 +8207,6 @@ snapshots: resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} - restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -8134,7 +8457,7 @@ snapshots: statuses@2.0.2: {} - std-env@3.10.0: {} + std-env@4.1.0: {} stdin-discarder@0.2.2: {} @@ -8177,10 +8500,6 @@ snapshots: strip-final-newline@4.0.0: {} - strip-literal@3.1.0: - dependencies: - js-tokens: 9.0.1 - style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -8207,8 +8526,6 @@ snapshots: tinybench@2.9.0: {} - tinyexec@0.3.2: {} - tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8216,11 +8533,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.4: {} + tinyrainbow@3.1.0: {} tldts-core@7.0.23: {} @@ -8265,10 +8578,9 @@ snapshots: tslib@2.8.1: {} - tsx@4.21.0: + tsx@4.22.4: dependencies: - esbuild: 0.27.3 - get-tsconfig: 4.13.6 + esbuild: 0.28.1 optionalDependencies: fsevents: 2.3.3 @@ -8419,39 +8731,18 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@3.2.4(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) transitivePeerDependencies: - supports-color - typescript - vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0): + vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -8464,54 +8755,39 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.31.1 - tsx: 4.21.0 + tsx: 4.22.4 - vitefu@1.1.1(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)): + vitefu@1.1.1(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)): optionalDependencies: - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) - - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@1.8.0))(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(tsx@4.21.0): - dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3 + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) + + vitest@4.1.7(@types/node@22.19.11)(jsdom@27.4.0(@noble/hashes@1.8.0))(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)): + dependencies: + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 + obug: 2.1.3 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.10.0 + std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) - vite-node: 3.2.4(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0) + tinyrainbow: 3.1.0 + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.22.4) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.12 '@types/node': 22.19.11 jsdom: 27.4.0(@noble/hashes@1.8.0) transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml w3c-xmlserializer@5.0.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index caa16aa..df0d9cc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ onlyBuiltDependencies: - esbuild + - lefthook - msw diff --git a/public/agent-guide.md b/public/agent-guide.md new file mode 100644 index 0000000..bc133bd --- /dev/null +++ b/public/agent-guide.md @@ -0,0 +1,98 @@ +# OTF agent guide + +Orientation for agents and analysts consuming the Ownership Token Framework +data. The framework evaluates how genuinely tokenholder-owned a crypto +protocol is, on evidence, against a fixed rubric. + +## The data model in one minute + +A **token's analysis** is a join of two things: + +- the **framework** β€” a fixed rubric of ~5 metrics, each with several + criteria (the *questions*, identical for every token). See + `/api/v1/framework`. +- that token's **evaluations** β€” one verdict per criterion: a status, notes, + and evidence (the *answers*). Delivered composed inside `/api/v1/tokens/{id}`. + +Criterion ids encode their place in the rubric: `onchain-ctrl__governance-workflow` += the `governance-workflow` criterion of the `onchain-ctrl` metric. + +## Endpoints + +| Endpoint | Use | +|---|---| +| `/api/v1/tokens` | discovery + cross-token comparison: slim rows with score, status counts, and a `criteriaStatuses` map keyed by criterion id | +| `/api/v1/tokens/{id}` | a full token report: `metrics[].criteria[]` with `status`, `notes`, `evidence[]`; plus `score` and counts | +| `/api/v1/framework` | the rubric: metric/criterion `name` + `about` definitions | +| `/api/v1/faq` | methodology and framework Q&A | + +`{id}` is the lowercase token id (e.g. `ldo`, `aave`). + +## Status vocabulary (exactly these) + +- `positive` β€” criterion met +- `warning` β€” partially met / caveats +- `at_risk` β€” not met (the terminal negative) +- `unevaluated` β€” not yet assessed +- `reference` β€” informational, not a judgment + +A token's `score` counts only evaluated, non-reference criteria. + +The per-token count fields predate this vocabulary and don't match it +one-to-one: `positive` = positive, **`neutral` = warning**, **`atRisk` = +at_risk**. `evidenceEntries` is the total number of evidence items across all +criteria. Trust `criteriaStatuses` / `criteria[].status` for the canonical +labels; treat the counts as pre-rolled tallies. + +## Provenance and reproducibility + +Every response is `{ data, provenance }`. `provenance.snapshot_id` is a +deterministic content hash of the entire published data set β€” cite it to pin +exactly what you read. `provenance.commit_ref` ties it to a deployment. +`provenance.last_updated` is when the content was last edited (ISO), distinct +from `published_at` β€” when this snapshot was published, which may be null in the +current serving mode (`provenance.source` tells you that mode, e.g. +`generated`). Use `snapshot_id` / `commit_ref` as the version of record. + +## Shape of a response + +Every endpoint returns `{ data, provenance }`. The index nests its rows under +`data.tokens`; a token doc is `data` directly. Trimmed: + +```jsonc +// GET /api/v1/tokens/ldo +{ + "data": { + "id": "ldo", "name": "Lido DAO", "symbol": "LDO", + "score": { "passing": 12, "total": 12, "percentage": 100 }, + "positive": 15, "neutral": 0, "atRisk": 0, "evidenceEntries": 26, + "criteriaStatuses": { "onchain-ctrl__governance-workflow": "positive" }, + "metrics": [{ + "id": "onchain-ctrl", "name": "Onchain Control", + "criteria": [{ + "id": "onchain-ctrl__governance-workflow", + "name": "Governance workflow", "status": "positive", + "notes": "…", + "evidence": [{ "name": "…", "summary": "…", + "urls": [{ "name": "Forum", "url": "https://…", "type": "vote" }] }] + }] + }] + }, + "provenance": { + "snapshot_id": "…", "commit_ref": "…", + "last_updated": "2026-05-13T12:21:49.000Z", + "published_at": null, "source": "generated" + } +} +``` + +## How to use it well + +- To analyze a protocol, fetch its token doc and follow the **evidence URLs** + (onchain explorers, docs, governance forums) β€” the framework is the map; + the evidence is the territory. +- To compare protocols, use the index's `criteriaStatuses` map β€” no need to + fetch every token doc. +- Statuses are the canonical machine signal; `notes` are human prose context. +- The data updates roughly quarterly; the same `snapshot_id` means the data + has not changed. diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..d936598 --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,30 @@ +# Ownership Token Framework + +> Structured, evidence-backed analysis of how genuinely tokenholder-owned crypto +> protocols are, by Aragon. Machine-readable, schema-validated, versioned. Every +> token is scored against one fixed rubric; every verdict is backed by linked +> evidence. + +## API (JSON, no auth) + +- `/api/v1/tokens` β€” index of all analyzed tokens (scores, status counts, per-criterion status map) +- `/api/v1/tokens/{id}` β€” full analysis for one token (metrics, criteria, notes, evidence); {id} is lowercase, e.g. ldo +- `/api/v1/framework` β€” the evaluation framework: metric and criterion definitions +- `/api/v1/faq` β€” framework and methodology Q&A + +## How to read it + +- Each response is `{ data, provenance }`. provenance carries snapshot_id + (a content hash of the whole data set), commit_ref, and last_updated (when + the content was last edited, distinct from published_at) β€” cite these for + reproducibility; do not infer freshness from cache headers. +- Criterion status is exactly one of: positive | warning | at_risk | + unevaluated | reference. There is no other vocabulary. (Index count fields + use older names: `neutral` = warning, `atRisk` = at_risk.) +- A token's analysis is the framework (shared rubric) filled in with that + token's evaluations; criterion ids are composite, e.g. + onchain-ctrl__governance-workflow. + +## Guide + +A fuller orientation for agents and analysts: /agent-guide.md diff --git a/scripts/build-data.ts b/scripts/build-data.ts new file mode 100644 index 0000000..9820cb0 --- /dev/null +++ b/scripts/build-data.ts @@ -0,0 +1,144 @@ +#!/usr/bin/env tsx +/** + * Build-time data layer. Produces src/data/generated/ (the read models the + * app serves) from otf-cms content. Three modes, by env: + * + * (default) no env set β†’ leave the committed src/data/generated/ + * as-is. Hermetic builds, local dev, and the test + * suite all use the committed snapshot. + * + * OTF_CONTENT_LOCAL=

compose from a local otf-cms checkout at

. + * Local development against sibling content. + * + * OTF_CONTENT_REF= fetch otf-cms content at (a branch/sha) and + * (+ OTF_CONTENT_TOKEN) compose it. This is the production + preview path: + * prod builds from `main`, PR previews from the + * content branch β€” one mechanism, no committed data + * churn, no write access to this repo. + * + * Composition uses the VENDORED composer (scripts/lib/compose-data.mjs), so + * the app produces byte-identical output to otf-cms. The composed output is + * Zod-validated against the shared contract BEFORE it is written, so an + * invalid ref (or composer/schema drift) fails the build instead of shipping + * unvalidated data into the deployment artifact. + */ +import { Buffer } from "node:buffer" +import { execFileSync } from "node:child_process" +import { + existsSync, + mkdirSync, + mkdtempSync, + readdirSync, + rmSync, + writeFileSync, +} from "node:fs" +import { tmpdir } from "node:os" +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" +// Vendored composer (TypeScript, same composition logic as otf-cms). +import { composeAll } from "./lib/compose-data" +import { + faqSchema, + frameworkDocSchema, + indexSchema, + manifestSchema, + testimonialsSchema, + tokenDocSchema, +} from "../src/lib/schemas/index" + +const root = join(dirname(fileURLToPath(import.meta.url)), "..") +const generatedDir = join(root, "src", "data", "generated") + +const ref = process.env.OTF_CONTENT_REF +const local = process.env.OTF_CONTENT_LOCAL + +if (!ref && !local) { + console.log( + "build-data: no OTF_CONTENT_REF / OTF_CONTENT_LOCAL β€” using committed src/data/generated/" + ) + process.exit(0) +} + +/** Resolve the otf-cms `content/` directory for this build. */ +async function resolveContentDir(): Promise<{ + contentDir: string + cleanup: () => void +}> { + if (local) { + const dir = join(local, "content") + if (!existsSync(dir)) throw new Error(`No content/ at ${local}`) + console.log(`build-data: composing from local checkout ${local}`) + return { contentDir: dir, cleanup: () => {} } + } + + const token = process.env.OTF_CONTENT_TOKEN + if (!token) throw new Error("OTF_CONTENT_REF set but OTF_CONTENT_TOKEN missing") + // Content repo, overridable so a fork builds from its own content. The + // tarball extracts to "--/", so the prefix tracks the repo. + const repo = process.env.OTF_CONTENT_REPO ?? "aragon/otf-cms" + const tmp = mkdtempSync(join(tmpdir(), "otf-content-")) + const tarball = join(tmp, "otf-cms.tar.gz") + console.log(`build-data: fetching ${repo}@${ref}`) + + // Native fetch β€” no shell. The token rides an Authorization header (never a + // command line), and the ref is URL-encoded into the path. GitHub's tarball + // API 302-redirects to a pre-signed codeload URL; fetch follows it. + const res = await fetch( + `https://api.github.com/repos/${repo}/tarball/${encodeURIComponent(ref as string)}`, + { + headers: { + Authorization: `Bearer ${token}`, + "User-Agent": "otf-app-build", + Accept: "application/vnd.github+json", + }, + } + ) + if (!res.ok) { + throw new Error(`fetch ${repo}@${ref} failed: ${res.status} ${res.statusText}`) + } + writeFileSync(tarball, Buffer.from(await res.arrayBuffer())) + + // execFileSync with an args array β€” no shell, so no metacharacter injection. + execFileSync("tar", ["xzf", tarball, "-C", tmp], { stdio: "inherit" }) + const extractedPrefix = `${repo.replace("/", "-")}-` + const extracted = readdirSync(tmp).find((n) => n.startsWith(extractedPrefix)) + if (!extracted) throw new Error("tarball did not contain the expected root") + return { + contentDir: join(tmp, extracted, "content"), + cleanup: () => rmSync(tmp, { recursive: true, force: true }), + } +} + +const { contentDir, cleanup } = await resolveContentDir() +try { + const { index, tokenDocs, frameworkDoc, faq, testimonials, manifest } = + composeAll(contentDir) + + // Validate against the shared contract before writing. Throwing here fails + // the build β€” invalid content never reaches the deployment artifact. + indexSchema.parse(index) + frameworkDocSchema.parse(frameworkDoc) + faqSchema.parse(faq) + testimonialsSchema.parse(testimonials) + manifestSchema.parse(manifest) + for (const doc of tokenDocs) tokenDocSchema.parse(doc) + + rmSync(generatedDir, { recursive: true, force: true }) + const write = (rel: string, data: unknown) => { + const p = join(generatedDir, rel) + mkdirSync(dirname(p), { recursive: true }) + writeFileSync(p, `${JSON.stringify(data, null, 2)}\n`) + } + write("index.json", index) + write("framework.json", frameworkDoc) + write("faq.json", faq) + write("testimonials.json", testimonials) + write("manifest.json", manifest) + for (const doc of tokenDocs) write(join("tokens", `${doc.id}.json`), doc) + + console.log( + `build-data: validated + composed ${tokenDocs.length} token docs (snapshot ${manifest.snapshot_id})` + ) +} finally { + cleanup() +} diff --git a/scripts/check-schema-drift.mjs b/scripts/check-schema-drift.mjs new file mode 100644 index 0000000..fd3560c --- /dev/null +++ b/scripts/check-schema-drift.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node +/** + * Drift gate (CI). The shared contract files here (the Zod schemas + composer) + * are checked-in COPIES of otf-cms's source β€” "vendored" by + * scripts/vendor-schemas.mjs. This verifies each copy still matches the hash + * recorded in the lock, so a hand-edit of a copy fails the build. To change + * one: edit it in otf-cms, then re-run vendor-schemas.mjs to refresh the copies. + */ +import { createHash } from "node:crypto" +import { readFileSync } from "node:fs" +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" + +const root = join(dirname(fileURLToPath(import.meta.url)), "..") +const lock = JSON.parse( + readFileSync(join(root, "src/lib/schemas/.vendor-lock.json"), "utf8") +) + +const drifted = [] +for (const [rel, expected] of Object.entries(lock.files)) { + let hash + try { + hash = createHash("sha256") + .update(readFileSync(join(root, rel), "utf8")) + .digest("hex") + } catch { + drifted.push(`${rel} (missing)`) + continue + } + if (hash !== expected) drifted.push(rel) +} + +if (drifted.length > 0) { + console.error( + `Vendored contract drift: ${drifted.join(", ")}\n` + + `These are vendored from aragon/otf-cms (locked at ${lock.source_commit.slice(0, 7)}). ` + + "Edit them in otf-cms, then run: node scripts/vendor-schemas.mjs" + ) + process.exit(1) +} + +console.log( + `vendored contract intact (${Object.keys(lock.files).length} files, ${lock.source}@${lock.source_commit.slice(0, 7)})` +) diff --git a/scripts/get-changed-token-ids.mjs b/scripts/get-changed-token-ids.mjs deleted file mode 100644 index 5a087af..0000000 --- a/scripts/get-changed-token-ids.mjs +++ /dev/null @@ -1,129 +0,0 @@ -import { readFile } from "node:fs/promises" -import { execSync } from "node:child_process" - -const TOKENS_PATH = "src/data/tokens.json" -const METRICS_PATH = "src/data/metrics.json" - -function parseArgs(argv) { - const beforeIndex = argv.indexOf("--before") - const afterIndex = argv.indexOf("--after") - return { - before: beforeIndex === -1 ? "" : argv[beforeIndex + 1] ?? "", - after: afterIndex === -1 ? "" : argv[afterIndex + 1] ?? "", - } -} - -function stableStringify(value) { - if (value === null || value === undefined) { - return "null" - } - if (Array.isArray(value)) { - return `[${value.map(stableStringify).join(",")}]` - } - if (typeof value === "object") { - const keys = Object.keys(value).sort() - return `{${keys - .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`) - .join(",")}}` - } - return JSON.stringify(value) -} - -function readJsonFile(path) { - return readFile(path, "utf-8").then((data) => JSON.parse(data)) -} - -function readJsonFromGit(before, path) { - try { - const data = execSync(`git show ${before}:${path}`, { - encoding: "utf-8", - stdio: ["ignore", "pipe", "ignore"], - }) - return JSON.parse(data) - } catch (error) { - return null - } -} - -function collectTokenMap(payload) { - if (!payload?.tokens || !Array.isArray(payload.tokens)) { - return new Map() - } - return new Map(payload.tokens.map((token) => [token.id, token])) -} - -function collectMetricsMap(payload) { - if (!payload || typeof payload !== "object") { - return new Map() - } - return new Map(Object.entries(payload)) -} - -const { before, after } = parseArgs(process.argv.slice(2)) -const zeroSha = /^0+$/ -const hasBefore = before && !zeroSha.test(before) -const hasAfter = after && !zeroSha.test(after) - -const [currentTokens, currentMetrics] = hasAfter - ? [ - readJsonFromGit(after, TOKENS_PATH), - readJsonFromGit(after, METRICS_PATH), - ] - : await Promise.all([readJsonFile(TOKENS_PATH), readJsonFile(METRICS_PATH)]) - -const previousTokens = hasBefore ? readJsonFromGit(before, TOKENS_PATH) : null -const previousMetrics = hasBefore ? readJsonFromGit(before, METRICS_PATH) : null - -if (hasAfter && (!currentTokens || !currentMetrics)) { - console.error( - "::warning::Unable to load after JSON from git; skipping updates." - ) - process.exit(0) -} - -if (hasBefore && (!previousTokens || !previousMetrics)) { - console.error( - "::warning::Unable to load before JSON from git; skipping updates." - ) - process.exit(0) -} - -const currentTokenMap = collectTokenMap(currentTokens) -const previousTokenMap = collectTokenMap(previousTokens) -const currentMetricsMap = collectMetricsMap(currentMetrics) -const previousMetricsMap = collectMetricsMap(previousMetrics) - -const changed = new Set() - -if (!hasBefore || !previousTokens) { - for (const id of currentTokenMap.keys()) { - changed.add(id) - } -} else { - const ids = new Set([...currentTokenMap.keys(), ...previousTokenMap.keys()]) - for (const id of ids) { - const beforeValue = previousTokenMap.get(id) - const afterValue = currentTokenMap.get(id) - if (stableStringify(beforeValue) !== stableStringify(afterValue)) { - changed.add(id) - } - } -} - -if (!hasBefore || !previousMetrics) { - for (const id of currentMetricsMap.keys()) { - changed.add(id) - } -} else { - const ids = new Set([...currentMetricsMap.keys(), ...previousMetricsMap.keys()]) - for (const id of ids) { - const beforeValue = previousMetricsMap.get(id) - const afterValue = currentMetricsMap.get(id) - if (stableStringify(beforeValue) !== stableStringify(afterValue)) { - changed.add(id) - } - } -} - -const output = Array.from(changed).filter(Boolean).join(",") -process.stdout.write(output) diff --git a/scripts/lib/compose-data.ts b/scripts/lib/compose-data.ts new file mode 100644 index 0000000..6816022 --- /dev/null +++ b/scripts/lib/compose-data.ts @@ -0,0 +1,231 @@ +#!/usr/bin/env tsx +/** + * Composer: content/ atoms β†’ src/data/generated/ read models. + * + * Joins research-shaped atoms (registry, framework, evaluations) into + * consumer-shaped docs by id: + * - criterion name/about come from framework (atom `name` overrides) + * - metric display name + about come from framework + * - scores and status counts are computed here (compose time), not at render + * + * Replaces the runtime framework-merge formerly in src/lib/metrics-data.ts. + * The scoring logic mirrors the pure functions in src/lib/scoring.ts and is + * pinned to them by the golden round-trip tests. + * + * Inputs are read from JSON (untyped by nature) and the composed output is + * Zod-validated downstream (otf-cms tests; the app's build-data step) β€” so the + * transform is typed loosely on purpose and the schema contract is the source + * of truth for the real shapes. + * + * Exports composeAll() for tests; run directly to write src/data/generated/. + */ +import { createHash } from "node:crypto" +import { + existsSync, + mkdirSync, + readdirSync, + 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 contentDir = join(root, "content") +const generatedDir = join(root, "generated") + +const readJson = (p: string): any => JSON.parse(readFileSync(p, "utf8")) + +/** + * Tolerant read for partial tokens: a token enters the set as soon as its + * identity file exists, but its criterion atoms / metric summaries may not + * exist yet (editors stand a token up progressively). A missing file is NOT a + * build error β€” it yields `fallback` so the doc composes fully with everything + * `unevaluated`/empty. The publish gate (bundle-snapshot) excludes such WIP + * tokens from Release; previews build on the partial data. + */ +const readJsonOr = (p: string, fallback: T): T => + existsSync(p) ? (readJson(p) as T) : fallback + +const SCORED_STATUSES = new Set(["positive", "warning", "at_risk"]) + +function getMetricScore(metric: any) { + const reference = metric.tags?.includes("Reference") ?? false + const evaluatedCriteria = metric.criteria.filter((c: any) => + SCORED_STATUSES.has(c.status) + ) + const total = evaluatedCriteria.length + const passing = evaluatedCriteria.filter( + (c: any) => c.status === "positive" + ).length + const evaluated = reference ? false : total > 0 + const percentage = total > 0 ? (passing / total) * 100 : 0 + + return { + metricId: metric.id, + metricName: metric.name, + passing, + total, + percentage, + evaluated, + reference, + } +} + +function getTokenScore(tokenId: string, metrics: any[]) { + const metricScores = metrics.map(getMetricScore) + const scoredMetrics = metricScores.filter((m) => m.evaluated && !m.reference) + const passing = scoredMetrics.reduce((sum, m) => sum + m.passing, 0) + const total = scoredMetrics.reduce((sum, m) => sum + m.total, 0) + const percentage = total > 0 ? (passing / total) * 100 : 0 + + return { tokenId, passing, total, percentage, metrics: metricScores } +} + +export function composeAll(dir: string = contentDir) { + const meta = readJson(join(dir, "framework-meta.json")) + const framework = meta.order.map((id: string) => + readJson(join(dir, "framework", `${id}.json`)) + ) + + const tokenIds: string[] = readdirSync(join(dir, "tokens")) + .filter((f) => f.endsWith(".json")) + .map((f) => f.replace(/\.json$/, "")) + .sort() + + const tokenAtoms = new Map( + tokenIds.map((id) => [id, readJson(join(dir, "tokens", `${id}.json`))]) + ) + + const tokenDocs: any[] = [] + for (const tokenId of tokenIds) { + const metrics: any[] = [] + for (const fm of framework) { + const metricDir = join(dir, "evaluations", tokenId, fm.id) + // Missing summary β†’ empty editorial (token still composing). + const editorial = readJsonOr<{ summary: string; tags: string[] }>( + join(dir, "summaries", tokenId, `${fm.id}.json`), + { summary: "", tags: [] } + ) + const criteria = fm.criteria.map((fc: any) => { + // Missing criterion atom β†’ unevaluated (no notes/evidence yet). + const atom = readJsonOr(join(metricDir, `${fc.id}.json`), { + status: "unevaluated", + notes: "", + }) + const composed: any = { + id: fc.id, + name: atom.name ?? fc.name, + about: fc.about, + status: atom.status, + notes: atom.notes ?? "", + } + if ("tags" in atom) composed.tags = atom.tags + if ("evidence" in atom) composed.evidence = atom.evidence + return composed + }) + metrics.push({ + id: fm.id, + name: fm.displayName, + about: fm.about, + summary: editorial.summary, + tags: editorial.tags, + criteria, + }) + } + + const allCriteria = metrics.flatMap((m) => m.criteria) + const countBy = (status: string) => + allCriteria.filter((c) => c.status === status).length + const score = getTokenScore(tokenId, metrics) + + tokenDocs.push({ + ...tokenAtoms.get(tokenId), + positive: countBy("positive"), + neutral: countBy("warning"), + atRisk: countBy("at_risk"), + evidenceEntries: allCriteria.flatMap((c) => c.evidence ?? []).length, + score, + criteriaStatuses: Object.fromEntries( + allCriteria.map((c) => [c.id, c.status]) + ), + metrics, + }) + } + + // Index rows are the dashboard read model: full score (the score hover + // shows the per-metric breakdown) and a flat criterion-status map (the + // table renders single-criterion columns across all tokens) β€” so the + // dashboard needs exactly one fetch and never loads per-token docs. + const index = { + tokens: tokenDocs.map(({ metrics: _metrics, ...row }) => row), + } + + const frameworkDoc = { baseUrl: meta.baseUrl, metrics: framework } + const faq = readJson(join(dir, "faq.json")) + const testimonials = readJson(join(dir, "testimonials.json")) + + // Deterministic snapshot identity: a content hash over the composed output. + // Stable across recompositions of identical content (keeps the freshness + // gate meaningful); changes exactly when published data changes. The future + // publish pipeline reuses this as the R2 snapshot key component. + const snapshotId = createHash("sha256") + .update( + JSON.stringify({ index, tokenDocs, frameworkDoc, faq, testimonials }) + ) + .digest("hex") + .slice(0, 16) + + // Most-recent editorial timestamp across the set (unix seconds β†’ ISO). + // Freshness signal surfaced in the API provenance envelope. Derived purely + // from content already inside snapshot_id's hash, so it never perturbs it. + const editedAt: number[] = tokenDocs + .map((d) => d.lastUpdated) + .filter((t) => typeof t === "number" && t > 0) + const manifest = { + snapshot_id: snapshotId, + last_updated: + editedAt.length > 0 + ? new Date(Math.max(...editedAt) * 1000).toISOString() + : null, + tokens: tokenDocs.map((d) => d.id as string), + } + + return { + index, + tokenDocs, + frameworkDoc, + faq, + testimonials, + manifest, + } +} + +function main() { + const { index, tokenDocs, frameworkDoc, faq, testimonials, manifest } = + composeAll() + + rmSync(generatedDir, { recursive: true, force: true }) + const writeJson = (p: string, data: unknown) => { + mkdirSync(dirname(p), { recursive: true }) + writeFileSync(p, `${JSON.stringify(data, null, 2)}\n`) + } + + writeJson(join(generatedDir, "index.json"), index) + for (const doc of tokenDocs) { + writeJson(join(generatedDir, "tokens", `${doc.id}.json`), doc) + } + writeJson(join(generatedDir, "framework.json"), frameworkDoc) + writeJson(join(generatedDir, "faq.json"), faq) + writeJson(join(generatedDir, "testimonials.json"), testimonials) + writeJson(join(generatedDir, "manifest.json"), manifest) + + console.log( + `composed: index (${index.tokens.length} rows), ${tokenDocs.length} token docs, framework, faq, testimonials (snapshot ${manifest.snapshot_id})` + ) +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main() +} diff --git a/scripts/sync-coingecko-ids.mjs b/scripts/sync-coingecko-ids.mjs deleted file mode 100644 index 8b85ad1..0000000 --- a/scripts/sync-coingecko-ids.mjs +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env node - -/** - * Sync CoinGecko IDs for tokens in tokens.json - * - * Uses CoinGecko's /coins/{platform}/contract/{address} endpoint - * to resolve the canonical CoinGecko ID from contract address + network. - * - * Usage: - * node scripts/sync-coingecko-ids.mjs # dry-run, skips tokens with existing IDs - * node scripts/sync-coingecko-ids.mjs --write # writes back to tokens.json - * node scripts/sync-coingecko-ids.mjs --force # re-verify all tokens (ignores cached IDs) - */ - -import { readFileSync, writeFileSync } from "node:fs" -import { resolve, dirname } from "node:path" -import { fileURLToPath } from "node:url" - -const __dirname = dirname(fileURLToPath(import.meta.url)) -const TOKENS_PATH = resolve(__dirname, "../src/data/tokens.json") - -// CoinGecko platform IDs mapped from our network names -const NETWORK_TO_PLATFORM = { - ethereum: "ethereum", - base: "base", - arbitrum: "arbitrum-one", - optimism: "optimistic-ethereum", - polygon: "polygon-pos", - avalanche: "avalanche", - bsc: "binance-smart-chain", -} - -const COINGECKO_API = "https://api.coingecko.com/api/v3" - -// Rate limit: CoinGecko free tier allows ~10-30 req/min -const DELAY_MS = 2500 - -const c = { - reset: "\x1b[0m", - dim: "\x1b[2m", - green: "\x1b[32m", - yellow: "\x1b[33m", - red: "\x1b[31m", - cyan: "\x1b[36m", - bold: "\x1b[1m", -} - -function sleep(ms) { - return new Promise((r) => setTimeout(r, ms)) -} - -async function lookupCoingeckoId(address, network) { - const platform = NETWORK_TO_PLATFORM[network] - if (!platform) { - return { error: `Unknown network: ${network}` } - } - - const url = `${COINGECKO_API}/coins/${platform}/contract/${address.toLowerCase()}` - const res = await fetch(url) - - if (!res.ok) { - return { error: `HTTP ${res.status} for ${address} on ${network}` } - } - - const data = await res.json() - return { id: data.id, name: data.name, symbol: data.symbol } -} - -async function main() { - const writeMode = process.argv.includes("--write") - const tokensFile = JSON.parse(readFileSync(TOKENS_PATH, "utf-8")) - const tokens = tokensFile.tokens - - let changes = 0 - - console.log(`\n${c.cyan}${c.bold} 🦎 CoinGecko Sync${c.reset}`) - console.log(`${c.dim} ─────────────────────────────${c.reset}\n`) - - const forceAll = process.argv.includes("--force") - const missing = tokens.filter((t) => !t.coingeckoId) - - if (!forceAll && missing.length === 0) { - console.log(` ${c.green}βœ“${c.reset} ${c.bold}${tokens.length} tokens${c.reset} synced ${c.dim}Β· no lookups needed${c.reset}\n`) - return - } - - const toProcess = forceAll ? tokens : missing - - for (const token of toProcess) { - process.stdout.write(` ${c.dim}${token.symbol}${c.reset} ${c.dim}${token.network}:${token.address.slice(0, 10)}…${c.reset} `) - - const result = await lookupCoingeckoId(token.address, token.network) - - if (result.error) { - console.log(`${c.red}βœ— ${result.error}${c.reset}`) - continue - } - - if (!token.coingeckoId) { - console.log(`${c.green}+ ${result.id}${c.reset}`) - token.coingeckoId = result.id - changes++ - } else if (token.coingeckoId !== result.id) { - console.log(`${c.yellow}β‰  ${token.coingeckoId} β†’ ${result.id}${c.reset}`) - token.coingeckoId = result.id - changes++ - } else { - console.log(`${c.green}βœ“ ${result.id}${c.reset}`) - } - - await sleep(DELAY_MS) - } - - console.log() - if (changes > 0 && writeMode) { - writeFileSync(TOKENS_PATH, JSON.stringify(tokensFile, null, 2) + "\n") - console.log(` ${c.green}βœ“${c.reset} Written ${c.bold}${changes}${c.reset} update(s) to tokens.json\n`) - } else if (changes > 0) { - console.log(` ${c.yellow}${changes}${c.reset} update(s) pending ${c.dim}Β· run with --write to save${c.reset}\n`) - } else { - console.log(` ${c.green}βœ“${c.reset} All verified ${c.dim}Β· no changes${c.reset}\n`) - } -} - -main().catch((err) => { - console.error(err) - process.exit(1) -}) diff --git a/scripts/update-token-timestamps.mjs b/scripts/update-token-timestamps.mjs deleted file mode 100644 index addab78..0000000 --- a/scripts/update-token-timestamps.mjs +++ /dev/null @@ -1,72 +0,0 @@ -import { readFile, writeFile } from "node:fs/promises" -import path from "node:path" - -const dataPath = path.resolve("src/data/tokens.json") - -function normalizeSymbol(value) { - return value.split("/").pop()?.trim().toUpperCase() ?? "" -} - -function parseArgs(argv) { - const args = new Set(argv) - const idsIndex = argv.indexOf("--ids") - const symbolIndex = argv.indexOf("--symbol") - const timestampIndex = argv.indexOf("--timestamp") - - const ids = - idsIndex === -1 ? null : argv[idsIndex + 1]?.split(",").filter(Boolean) - const symbol = symbolIndex === -1 ? "" : argv[symbolIndex + 1] ?? "" - const timestampArg = timestampIndex === -1 ? null : argv[timestampIndex + 1] - - return { - updateAll: args.has("--all") || (!ids && !symbol), - ids, - symbol: normalizeSymbol(symbol), - timestamp: timestampArg ? Number(timestampArg) : null, - } -} - -const { updateAll, ids, symbol, timestamp } = parseArgs(process.argv.slice(2)) -const nextTimestamp = Number.isFinite(timestamp) - ? timestamp - : Math.floor(Date.now() / 1000) - -const raw = await readFile(dataPath, "utf-8") -const payload = JSON.parse(raw) - -if (!payload?.tokens || !Array.isArray(payload.tokens)) { - throw new Error("Expected src/data/tokens.json to contain a tokens array") -} - -const idSet = ids ? new Set(ids.map((id) => id.toLowerCase())) : null -let updated = 0 - -payload.tokens = payload.tokens.map((token) => { - if (updateAll) { - updated += 1 - return { ...token, lastUpdated: nextTimestamp } - } - - if (idSet && idSet.has(token.id.toLowerCase())) { - updated += 1 - return { ...token, lastUpdated: nextTimestamp } - } - - if (symbol && token.symbol.toUpperCase() === symbol) { - updated += 1 - return { ...token, lastUpdated: nextTimestamp } - } - - return token -}) - -if (updated === 0) { - console.log("No tokens matched; nothing updated.") - process.exit(0) -} - -await writeFile(dataPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8") - -console.log( - `Updated ${updated} token${updated === 1 ? "" : "s"} to ${nextTimestamp}.` -) diff --git a/scripts/vendor-schemas.mjs b/scripts/vendor-schemas.mjs new file mode 100644 index 0000000..45337aa --- /dev/null +++ b/scripts/vendor-schemas.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node +/** + * Sync the shared data contract (Zod schemas + composer) from otf-cms into this + * repo by COPYING the files in. This is "vendoring": the app keeps checked-in + * copies of otf-cms's source instead of installing it as an npm package, so + * there's one source of truth (otf-cms) with no registry or publish step. To + * "re-vendor" is just to re-run this script to refresh the copies after the + * otf-cms originals change. + * + * Usage: node scripts/vendor-schemas.mjs [path-to-otf-cms-checkout] + * (default: ../otf-cms sibling checkout) + * + * Copies: + * otf-cms/schemas/*.ts β†’ src/lib/schemas/ (the Zod contract) + * otf-cms/scripts/compose-data.ts β†’ scripts/lib/compose-data.ts (the composer) + * + * Writes a lock file (the otf-cms source commit + a hash of each copied file). + * CI (check-schema-drift.mjs) re-checks those hashes, so hand-editing a copy + * here fails the build β€” change it in otf-cms and re-run this script instead. + */ +import { execSync } from "node:child_process" +import { createHash } from "node:crypto" +import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs" +import { dirname, join, resolve } from "node:path" +import { fileURLToPath } from "node:url" + +const root = join(dirname(fileURLToPath(import.meta.url)), "..") +const sourceRepo = resolve(root, process.argv[2] ?? "../otf-cms") + +const BANNER = (commit) => `/** + * OTF data contract β€” a checked-in COPY of otf-cms's source (i.e. "vendored"). + * DO NOT EDIT HERE: a change to this copy fails CI's drift check. + * + * Source of truth: https://github.com/aragon/otf-cms (copied at ${commit}). + * To change it, edit the file in otf-cms, then refresh the copies here by + * re-running: node scripts/vendor-schemas.mjs + */` + +const sourceCommit = execSync("git rev-parse HEAD", { + cwd: sourceRepo, + encoding: "utf8", +}).trim() + +const lock = { source: "aragon/otf-cms", source_commit: sourceCommit, files: {} } + +/** Copy a source file to a repo-root-relative target; record its hash. */ +function vendor(srcAbs, targetRel, { banner = false } = {}) { + let content = readFileSync(srcAbs, "utf8") + if (banner) content = content.replace(/^\/\*\*[\s\S]*?\*\//, BANNER(sourceCommit)) + const targetAbs = join(root, targetRel) + mkdirSync(dirname(targetAbs), { recursive: true }) + writeFileSync(targetAbs, content) + lock.files[targetRel] = createHash("sha256").update(content).digest("hex") +} + +// Schemas +const schemaDir = join(sourceRepo, "schemas") +for (const f of readdirSync(schemaDir).filter((f) => f.endsWith(".ts")).sort()) { + vendor(join(schemaDir, f), join("src/lib/schemas", f), { banner: f === "index.ts" }) +} + +// Composer +vendor( + join(sourceRepo, "scripts", "compose-data.ts"), + "scripts/lib/compose-data.ts" +) + +writeFileSync( + join(root, "src/lib/schemas/.vendor-lock.json"), + `${JSON.stringify(lock, null, 2)}\n` +) + +console.log( + `vendored ${Object.keys(lock.files).length} files from otf-cms@${sourceCommit.slice(0, 7)}` +) diff --git a/src/components/evidence-card.tsx b/src/components/evidence-card.tsx index b3fcdfc..3a13abc 100644 --- a/src/components/evidence-card.tsx +++ b/src/components/evidence-card.tsx @@ -1,6 +1,7 @@ import ReactMarkdown from "react-markdown" import remarkBreaks from "remark-breaks" import type { Evidence, EvidenceUrl } from "@/lib/metrics-data" +import { isPlaceholder } from "@/lib/utils" import { EvidenceLink } from "./ui/evidence-link.tsx" interface MarkdownComponentProps { @@ -32,18 +33,25 @@ interface IEvidenceCardProps { export const EvidenceCard: React.FC = (props) => { const { evidence } = props + // "TK"/empty urls are placeholders from WIP tokens β€” never render them as + // live links (only the resolved ones are shown). + const urls = evidence.urls.filter((url) => !isPlaceholder(url.url)) + return (

- {(evidence.name || evidence.summary) && ( + {(!isPlaceholder(evidence.name) || !isPlaceholder(evidence.summary)) && (
- {evidence.name && ( + {!isPlaceholder(evidence.name) && (

{evidence.name}

)} - {evidence.summary && ( + {!isPlaceholder(evidence.summary) && (
- + {evidence.summary}
@@ -51,13 +59,13 @@ export const EvidenceCard: React.FC = (props) => {
)}
- {evidence.urls.map((url, index) => ( + {urls.map((url, index) => ( - {url.name} + {isPlaceholder(url.name) ? url.url : url.name} ))}
diff --git a/src/components/info-sidebar.tsx b/src/components/info-sidebar.tsx index f5b3b8e..46d66c3 100644 --- a/src/components/info-sidebar.tsx +++ b/src/components/info-sidebar.tsx @@ -6,7 +6,7 @@ import { // @ts-expect-error by default it imports from cjs build and triggers server-side error } from "@tabler/icons-react/dist/esm/tabler-icons-react.mjs" import { useEffect, useState } from "react" -import { copyToClipboard, truncateAddress } from "@/lib/utils" +import { copyToClipboard, isPlaceholder, truncateAddress } from "@/lib/utils" import type { TokenInfo } from "./token-detail" import { Button } from "./ui/button" import { ExplorerIcon } from "./ui/explore-icon.tsx" @@ -100,7 +100,7 @@ export default function InfoSidebar({ token }: { token: TokenInfo }) { )} {truncateAddress(token.address)} - {token.links.scan && ( + {!isPlaceholder(token.links.scan) && (
- {metric.summary && ( -

and nested + // paragraphs are invalid HTML (causes hydration mismatches) +

- + {metric.summary} -

+
)} @@ -195,26 +200,30 @@ export default function MetricCard(props: MetricCardProps) { variant="h4" /> - {!score.reference && ( - - )} + {!score.reference && }
{match(criteria.notes) - .with(P.string, (notes) => ( - //
-
- - {notes} - -
- )) + .with( + P.string.and(P.when((notes) => !isPlaceholder(notes))), + (notes) => ( + //
+
+ + {notes} + +
+ ) + ) .otherwise(() => null)} {match(criteria.evidence) .with(P.union(P.nullish, []), () => null) diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 8a8eade..9a3e1d0 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -23,8 +23,8 @@ import { NavigationMenuList, } from "@/components/ui/navigation-menu" import { useTokenSearch } from "@/hooks/use-token-search" -import { FRAMEWORK_BASE_URL } from "@/lib/framework" -import { cn } from "@/lib/utils" +import { getFrameworkBaseUrl } from "@/lib/framework" +import { cn, isPlaceholder } from "@/lib/utils" export function SiteHeader() { const [submitDialogOpen, setSubmitDialogOpen] = useState(false) @@ -115,7 +115,7 @@ export function SiteHeader() { } + render={} > Framework @@ -187,7 +187,14 @@ export function SiteHeader() { to="/tokens/$tokenId" > - + {token.name.slice(0, 2)} @@ -216,7 +223,7 @@ export function SiteHeader() { Framework @@ -288,7 +295,12 @@ export function SiteHeader() { to="/tokens/$tokenId" > - + {token.name.slice(0, 2)} diff --git a/src/components/token-detail.tsx b/src/components/token-detail.tsx index 9fff248..d408c54 100644 --- a/src/components/token-detail.tsx +++ b/src/components/token-detail.tsx @@ -16,18 +16,14 @@ import { useMarketData } from "@/hooks/use-market-data" import { trackExpandAllCriteria } from "@/lib/analytics" import { getMetricsByTokenId, type Metric } from "@/lib/metrics-data" import { getTokenById } from "@/lib/token-data" -import { formatUnixTimestamp } from "@/lib/utils" +import { formatUnixTimestamp, isPlaceholder } from "@/lib/utils" import AnalyticsContent from "./analytics-content" import InfoSidebar from "./info-sidebar" import { NewsletterSignup } from "./newsletter-signup.tsx" import { OwnershipScoreCard } from "./ownership-score-card" // Types -export type { - CriteriaStatusValue as CriteriaStatus, -} from "@/lib/metrics-data" - -export { normalizeCriteriaStatus as mapStatus } from "@/lib/metrics-data" +export type { CriteriaStatusValue as CriteriaStatus } from "@/lib/metrics-data" export interface TokenInfo { id: string @@ -77,7 +73,10 @@ function TokenHero({ token }: { token: TokenInfo }) {
- + {token.name.slice(0, 2)} diff --git a/src/components/token-ownership-analytics.tsx b/src/components/token-ownership-analytics.tsx index 2ddf207..4930617 100644 --- a/src/components/token-ownership-analytics.tsx +++ b/src/components/token-ownership-analytics.tsx @@ -1,5 +1,10 @@ "use client" +import { + IconCircleCheckFilled, + IconCircleX, + // @ts-expect-error by default it imports from cjs build and triggers server-side error +} from "@tabler/icons-react/dist/esm/tabler-icons-react.mjs" import { Link, useNavigate } from "@tanstack/react-router" import { type ColumnDef, @@ -11,11 +16,6 @@ import { type SortingState, useReactTable, } from "@tanstack/react-table" -import { - IconCircleCheckFilled, - IconCircleX, - // @ts-expect-error by default it imports from cjs build and triggers server-side error -} from "@tabler/icons-react/dist/esm/tabler-icons-react.mjs" import { ArrowRightIcon, ChevronLeftIcon, @@ -60,7 +60,11 @@ import { type EnrichedToken, useMarketData } from "@/hooks/use-market-data" import { useTokens } from "@/hooks/use-tokens" import { CRITERIA_STATUS, getCriteriaStatus } from "@/lib/metrics-data" import { getTokenOwnershipScore } from "@/lib/scoring" -import { formatUnixTimestamp, truncateAddress } from "@/lib/utils" +import { + formatUnixTimestamp, + isPlaceholder, + truncateAddress, +} from "@/lib/utils" function formatMarketCap(value?: number): string { if (value == null) return "β€”" @@ -159,7 +163,12 @@ const columns: ColumnDef[] = [ cell: ({ row }) => (
- + {row.original.name.slice(0, 2)} diff --git a/src/components/ui/evidence-link.tsx b/src/components/ui/evidence-link.tsx index d71724d..094a5cd 100644 --- a/src/components/ui/evidence-link.tsx +++ b/src/components/ui/evidence-link.tsx @@ -1,16 +1,27 @@ import { FileTextIcon, GithubIcon } from "lucide-react" import type { ComponentProps } from "react" -import { cn } from "@/lib/utils" +import { cn, isPlaceholder } from "@/lib/utils" import { ExplorerIcon } from "./explore-icon.tsx" -export type EvidenceLinkType = "github" | "docs" | "explorer" +export type EvidenceLinkType = + | "github" + | "docs" + | "explorer" + | "vote" + | "website" interface IEvidenceLinkProps extends ComponentProps<"a"> { type?: EvidenceLinkType } export const EvidenceLink: React.FC = (props) => { - const { type = "generic", className, children, ...otherProps } = props + const { type = "generic", className, children, href, ...otherProps } = props + + // A "TK"/empty href is a placeholder from a WIP token β€” never emit a dead + // link (callers already filter these out; this is a defensive guard). + if (isPlaceholder(href)) { + return null + } const renderIcon = () => { switch (type) { @@ -35,6 +46,7 @@ export const EvidenceLink: React.FC = (props) => { "transition-colors duration-200", className )} + href={href} rel="noopener noreferrer" target="_blank" {...otherProps} diff --git a/src/components/ui/title-popover.tsx b/src/components/ui/title-popover.tsx index 45a7810..c114e96 100644 --- a/src/components/ui/title-popover.tsx +++ b/src/components/ui/title-popover.tsx @@ -41,9 +41,7 @@ const TitlePopover: React.FC = (props) => { )} {...otherProps} > - +
- + {item.answer}
diff --git a/src/data/framework.json b/src/data/framework.json deleted file mode 100644 index 6070e77..0000000 --- a/src/data/framework.json +++ /dev/null @@ -1,127 +0,0 @@ -[ - { - "id": "onchain-ctrl", - "name": "Metric 1: Onchain Control", - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions." - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders." - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders." - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance." - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes." - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users." - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers." - } - ] - }, - { - "id": "val-accrual", - "name": "Metric 2: Value Accrual", - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed." - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance." - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing." - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "about": "Are there additional offchain value accrual flows that benefit tokenholders?" - } - ] - }, - { - "id": "verifiability", - "name": "Metric 3: Verifiability", - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode." - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments." - } - ] - }, - { - "id": "distribution", - "name": "Metric 4: Token Distribution", - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply." - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?" - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?" - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?" - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?" - } - ] - } -] diff --git a/src/data/faq.json b/src/data/generated/faq.json similarity index 100% rename from src/data/faq.json rename to src/data/generated/faq.json diff --git a/src/data/generated/framework.json b/src/data/generated/framework.json new file mode 100644 index 0000000..7ff73b9 --- /dev/null +++ b/src/data/generated/framework.json @@ -0,0 +1,140 @@ +{ + "baseUrl": "https://github.com/aragon/ownership-token-framework/blob/development/README.md", + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Metric 1: Onchain Control", + "displayName": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "anchor": "#metric-1-onchain-control", + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions." + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders." + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders." + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance." + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes." + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users." + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers." + } + ] + }, + { + "id": "val-accrual", + "name": "Metric 2: Value Accrual", + "displayName": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "anchor": "#metric-2-value-accrual", + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed." + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance." + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing." + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?" + } + ] + }, + { + "id": "verifiability", + "name": "Metric 3: Verifiability", + "displayName": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "anchor": "#metric-3-verifiability", + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode." + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments." + } + ] + }, + { + "id": "distribution", + "name": "Metric 4: Token Distribution", + "displayName": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "anchor": "#metric-4-token-distribution", + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply." + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?" + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "displayName": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "anchor": "#offchain-dependencies", + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?" + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?" + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?" + } + ] + } + ] +} diff --git a/src/data/generated/index.json b/src/data/generated/index.json new file mode 100644 index 0000000..d4b359e --- /dev/null +++ b/src/data/generated/index.json @@ -0,0 +1,1082 @@ +{ + "tokens": [ + { + "id": "aave", + "coingeckoId": "aave", + "name": "AAVE", + "symbol": "AAVE", + "address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", + "icon": "https://assets.coingecko.com/coins/images/12645/standard/aave-token-round.png", + "description": "AAVE is the native token of Aave, the largest lending protocol in DeFi.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://aave.com/", + "twitter": "https://twitter.com/aave", + "scan": "https://etherscan.io/token/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9" + }, + "infoDescription": "Aave is an Open Source Protocol to create Non-Custodial Liquidity Markets to earn interest on supplying and borrowing assets with a variable interest rate.", + "positive": 12, + "neutral": 2, + "atRisk": 0, + "evidenceEntries": 18, + "score": { + "tokenId": "aave", + "passing": 12, + "total": 12, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 0, + "percentage": 0, + "evaluated": false, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "unevaluated", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + } + }, + { + "id": "aero", + "coingeckoId": "aerodrome-finance", + "name": "AERO", + "symbol": "AERO", + "address": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", + "icon": "https://assets.coingecko.com/coins/images/31745/standard/token.png", + "description": "AERO is the native token of the Aerodrome protocol, a ve(3,3) MetaDex on Base with largely immutable contracts and programmatic value flows to veAERO holders.", + "network": "base", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://aerodrome.finance/", + "twitter": "https://twitter.com/Aerodrome", + "scan": "https://basescan.org/token/0x940181a94A35A4569E4529A3CDfB74e38FD98631" + }, + "infoDescription": "Aerodrome is a decentralized exchange where you can execute low-fee swaps, deposit tokens to earn rewards, and actively participate in the onchain economy.", + "positive": 12, + "neutral": 3, + "atRisk": 0, + "evidenceEntries": 23, + "score": { + "tokenId": "aero", + "passing": 12, + "total": 13, + "percentage": 92.3076923076923, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 6, + "total": 7, + "percentage": 85.71428571428571, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "warning", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + } + }, + { + "id": "crv", + "coingeckoId": "curve-dao-token", + "name": "CRV", + "symbol": "CRV", + "address": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "icon": "https://assets.coingecko.com/coins/images/12124/standard/Curve.png", + "description": "CRV is the native token of the Curve protocol - a leading stablecoin DEX. The veCRV gauge system enables programmatic, onchain value direction by tokenholders.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://curve.finance/", + "twitter": "https://twitter.com/curvefinance", + "scan": "https://etherscan.io/token/0xD533a949740bb3306d119CC777fa900bA034cd52" + }, + "infoDescription": "Curve is a decentralized exchange (DEX) and automated market maker (AMM) on Ethereum and EVM-compatible sidechains/L2s, designed for the efficient trading of stablecoins and volatile assets.", + "positive": 13, + "neutral": 2, + "atRisk": 0, + "evidenceEntries": 23, + "score": { + "tokenId": "crv", + "passing": 13, + "total": 13, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + } + }, + { + "id": "ena", + "coingeckoId": "ethena", + "name": "ENA", + "symbol": "ENA", + "address": "0x57e114B691Db790C35207b2e685D4A43181e6061", + "icon": "https://assets.coingecko.com/coins/images/36530/standard/ethena.png", + "description": "ENA is the governance token of Ethena, a synthetic dollar protocol. Governance is advisory via Snapshot signaling; the Dev Multisig executes all decisions. Fee switch received positive forum signals but awaits Snapshot vote and onchain execution.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://ethena.fi/", + "twitter": "https://twitter.com/ethena", + "scan": "https://etherscan.io/token/0x57e114B691Db790C35207b2e685D4A43181e6061" + }, + "infoDescription": "Ethena is a synthetic dollar protocol built on Ethereum, offering a crypto-native alternative to traditional stablecoins via its USDe token and yield-bearing sUSDe.", + "positive": 2, + "neutral": 16, + "atRisk": 0, + "evidenceEntries": 29, + "score": { + "tokenId": "ena", + "passing": 2, + "total": 15, + "percentage": 13.333333333333334, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 0, + "total": 7, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 0, + "total": 4, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "warning", + "onchain-ctrl__role-accountability": "warning", + "onchain-ctrl__protocol-upgrade": "warning", + "onchain-ctrl__token-upgrade": "warning", + "onchain-ctrl__supply": "warning", + "onchain-ctrl__access-gating": "warning", + "onchain-ctrl__censorship": "warning", + "val-accrual__active": "warning", + "val-accrual__treasury": "warning", + "val-accrual__mechanism": "warning", + "val-accrual__offchain": "warning", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "warning", + "distribution__supply-schedule": "warning", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + } + }, + { + "id": "ethfi", + "coingeckoId": "ether-fi", + "name": "ETHFI", + "symbol": "ETHFI", + "address": "0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB", + "icon": "https://assets.coingecko.com/coins/images/35958/standard/etherfi.jpeg", + "description": "EtherFi runs restaking infrastructure, liquid vaults, and savings products β€” expanding from LST into neobank territory. Governance is offchain with multisig execution. An active buyback program distributes purchased ETHFI to sETHFI holders; any funding from broader protocol revenue is currently discretionary.", + "network": "ethereum", + "lastUpdated": 1774549304, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://ether.fi/", + "twitter": "https://twitter.com/ether_fi", + "scan": "https://etherscan.io/token/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB" + }, + "infoDescription": "ether.fi is a liquid restaking protocol that enables users to stake ETH while maintaining liquidity through eETH tokens and participating in EigenLayer restaking.", + "positive": 3, + "neutral": 13, + "atRisk": 1, + "evidenceEntries": 20, + "score": { + "tokenId": "ethfi", + "passing": 3, + "total": 14, + "percentage": 21.428571428571427, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 1, + "total": 7, + "percentage": 14.285714285714285, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 1, + "total": 3, + "percentage": 33.33333333333333, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 1, + "total": 2, + "percentage": 50, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "at_risk", + "onchain-ctrl__role-accountability": "warning", + "onchain-ctrl__protocol-upgrade": "warning", + "onchain-ctrl__token-upgrade": "warning", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "warning", + "onchain-ctrl__censorship": "warning", + "val-accrual__active": "positive", + "val-accrual__treasury": "warning", + "val-accrual__mechanism": "warning", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "warning", + "verifiability__protocol-source": "positive", + "distribution__concentration": "warning", + "distribution__supply-schedule": "warning", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + } + }, + { + "id": "ldo", + "coingeckoId": "lido-dao", + "name": "LDO", + "symbol": "LDO", + "address": "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32", + "icon": "https://assets.coingecko.com/coins/images/13573/standard/Lido_DAO.png", + "description": "LDO is the native token of Lido, a liquid staking protocol with onchain voting, safeguarded by stETH holders via Dual Governance.", + "network": "ethereum", + "lastUpdated": 1777631939, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://lido.fi/", + "twitter": "https://twitter.com/lidofinance", + "scan": "https://etherscan.io/token/0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32" + }, + "infoDescription": "Lido is the leading liquid staking solution for Ethereum.", + "positive": 15, + "neutral": 0, + "atRisk": 0, + "evidenceEntries": 26, + "score": { + "tokenId": "ldo", + "passing": 12, + "total": 12, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 0, + "percentage": 0, + "evaluated": false, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "unevaluated", + "offchain__trademark": "positive", + "offchain__distribution": "positive", + "offchain__licensing": "positive" + } + }, + { + "id": "lqty", + "coingeckoId": "liquity", + "name": "LQTY", + "symbol": "LQTY", + "address": "0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D", + "icon": "https://assets.coingecko.com/coins/images/14665/standard/logo_V2.png", + "description": "LQTY is the secondary token of the Liquity protocol - a decentralised, immutable, and governance-free borrowing protocol that issues the LUSD (V1) and BOLD (V2) stablecoins.", + "network": "ethereum", + "lastUpdated": 1778064256, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://www.liquity.org/", + "twitter": "https://twitter.com/LiquityProtocol", + "scan": "https://etherscan.io/token/0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D" + }, + "infoDescription": "Liquity is a decentralised, immutable borrowing protocol that lets users mint stablecoins (LUSD in V1, BOLD in V2) against ETH and LST collateral with no governance over core parameters.", + "positive": 13, + "neutral": 1, + "atRisk": 2, + "evidenceEntries": 16, + "score": { + "tokenId": "lqty", + "passing": 13, + "total": 13, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "at_risk", + "offchain__distribution": "warning", + "offchain__licensing": "at_risk" + } + }, + { + "id": "ondo", + "name": "ONDO", + "symbol": "ONDO", + "address": "0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3", + "icon": "https://assets.coingecko.com/coins/images/26580/standard/ONDO.png", + "description": "Ondo Finance tokenizes real-world assets: OUSG (US Treasuries), USDY (yield-bearing stablecoin), and Global Markets (tokenized equities). ONDO token has no control over these core products; team multisigs govern all ~$3.5B TVL.", + "network": "ethereum", + "lastUpdated": 1778674909, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://ondo.finance/", + "twitter": "https://twitter.com/OndoFinance", + "scan": "https://etherscan.io/token/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3" + }, + "infoDescription": "Ondo Finance is a tokenized real-world asset (RWA) protocol with ~$3.56B TVL across OUSG, USDY, and Global Markets. ONDO token governs Flux Finance (~$43M TVL), a Compound V2 fork for permissioned lending.", + "coingeckoId": "ondo-finance", + "positive": 4, + "neutral": 4, + "atRisk": 9, + "evidenceEntries": 27, + "score": { + "tokenId": "ondo", + "passing": 4, + "total": 15, + "percentage": 26.666666666666668, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 2, + "total": 7, + "percentage": 28.57142857142857, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 0, + "total": 4, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "at_risk", + "onchain-ctrl__role-accountability": "at_risk", + "onchain-ctrl__protocol-upgrade": "at_risk", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "at_risk", + "onchain-ctrl__access-gating": "at_risk", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "at_risk", + "val-accrual__treasury": "at_risk", + "val-accrual__mechanism": "at_risk", + "val-accrual__offchain": "warning", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "at_risk", + "distribution__supply-schedule": "warning", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + } + }, + { + "id": "sky", + "coingeckoId": "sky", + "name": "SKY", + "symbol": "SKY", + "address": "0x56072C95FAA701256059aa122697B133aDEd9279", + "icon": "https://assets.coingecko.com/coins/images/39925/standard/sky.jpg", + "description": "SKY demonstrates strong ownership characteristics with binding onchain governance and active value accrual through buybacks. The token is non-upgradeable with no censorship capabilities. Trademarks remain with an independent foundation.", + "network": "ethereum", + "lastUpdated": 1774549304, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://sky.money/", + "twitter": "https://twitter.com/SkyEcosystem", + "scan": "https://etherscan.io/token/0x56072C95FAA701256059aa122697B133aDEd9279" + }, + "infoDescription": "Sky Protocol (formerly MakerDAO) issues two stablecoins: DAI (non-upgradeable) and USDS (upgradeable).", + "positive": 13, + "neutral": 1, + "atRisk": 0, + "evidenceEntries": 25, + "score": { + "tokenId": "sky", + "passing": 12, + "total": 12, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 0, + "percentage": 0, + "evaluated": false, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 1, + "total": 2, + "percentage": 50, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "unevaluated", + "offchain__trademark": "warning", + "offchain__distribution": "unevaluated", + "offchain__licensing": "positive" + } + }, + { + "id": "uni", + "coingeckoId": "uniswap", + "name": "UNI", + "symbol": "UNI", + "address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + "icon": "https://assets.coingecko.com/coins/images/12504/standard/uniswap-logo.png", + "description": "UNI is the governance token of the Uniswap DAO, the DAO governing the Uniswap protocol - the largest decentralised exchange in DeFi.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://uniswap.org/", + "twitter": "https://twitter.com/uniswap", + "scan": "https://etherscan.io/token/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" + }, + "infoDescription": "Uniswap is the largest onchain marketplace. Buy and sell crypto on Ethereum and 16+ other chains.", + "positive": 13, + "neutral": 3, + "atRisk": 0, + "evidenceEntries": 25, + "score": { + "tokenId": "uni", + "passing": 13, + "total": 13, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + } + }, + { + "id": "yb", + "coingeckoId": "yield-basis", + "name": "YB", + "symbol": "YB", + "address": "0x01791F726B4103694969820be083196cC7c045fF", + "icon": "https://coin-images.coingecko.com/coins/images/54871/small/yieldbasis_400x400.png", + "description": "YB is the token of YieldBasis. veYB holders control protocol governance through Aragon, direct YB emissions via gauge voting, and receive protocol admin fees.", + "network": "ethereum", + "lastUpdated": 1774549304, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://yieldbasis.com/", + "twitter": "https://x.com/yieldbasis", + "scan": "https://etherscan.io/token/0x01791F726B4103694969820be083196cC7c045fF" + }, + "infoDescription": "Yield Basis is the liquidity protocol designed to eliminate Impermanent Loss (IL) in AMMs using constantly-maintained 2x leveraged liquidity provision.", + "positive": 11, + "neutral": 4, + "atRisk": 1, + "evidenceEntries": 18, + "score": { + "tokenId": "yb", + "passing": 11, + "total": 14, + "percentage": 78.57142857142857, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 2, + "total": 3, + "percentage": 66.66666666666666, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "at_risk", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "warning", + "distribution__supply-schedule": "warning", + "offchain__trademark": "unevaluated", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + } + } + ] +} diff --git a/src/data/generated/manifest.json b/src/data/generated/manifest.json new file mode 100644 index 0000000..b797b06 --- /dev/null +++ b/src/data/generated/manifest.json @@ -0,0 +1,17 @@ +{ + "snapshot_id": "364589340247aa7f", + "last_updated": "2026-05-13T12:21:49.000Z", + "tokens": [ + "aave", + "aero", + "crv", + "ena", + "ethfi", + "ldo", + "lqty", + "ondo", + "sky", + "uni", + "yb" + ] +} diff --git a/src/data/testimonials.json b/src/data/generated/testimonials.json similarity index 100% rename from src/data/testimonials.json rename to src/data/generated/testimonials.json diff --git a/src/data/generated/tokens/aave.json b/src/data/generated/tokens/aave.json new file mode 100644 index 0000000..67deb76 --- /dev/null +++ b/src/data/generated/tokens/aave.json @@ -0,0 +1,582 @@ +{ + "id": "aave", + "coingeckoId": "aave", + "name": "AAVE", + "symbol": "AAVE", + "address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", + "icon": "https://assets.coingecko.com/coins/images/12645/standard/aave-token-round.png", + "description": "AAVE is the native token of Aave, the largest lending protocol in DeFi.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://aave.com/", + "twitter": "https://twitter.com/aave", + "scan": "https://etherscan.io/token/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9" + }, + "infoDescription": "Aave is an Open Source Protocol to create Non-Custodial Liquidity Markets to earn interest on supplying and borrowing assets with a variable interest rate.", + "positive": 12, + "neutral": 2, + "atRisk": 0, + "evidenceEntries": 18, + "score": { + "tokenId": "aave", + "passing": 12, + "total": 12, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 0, + "percentage": 0, + "evaluated": false, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "unevaluated", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "AAVE, stkAAVE and aAAVE holders control the protocol through onchain governance with Timelock execution. The token has fixed 16M supply with no mint function. Delegated steward roles exist but are elected and revocable by governance.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "AAVE, stkAAVE and aAAVE holders vote onchain with execution through Timelock.", + "evidence": [ + { + "urls": [ + { + "name": "Governance Documentation", + "url": "https://aave.com/docs/ecosystem/governance", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "The Governance Emergency Guardian is a 5/9 multisig elected by holders to protect the protocol from governance takeover attacks by vetoing onchain payloads.", + "evidence": [ + { + "name": "Governance Emergency Guardian", + "urls": [ + { + "name": "Onchain address", + "url": "https://etherscan.io/address/0xCe52ab41C40575B072A18C9700091Ccbe4A06710#readProxyContract", + "type": "explorer" + }, + { + "name": "Detailed Governance Permissions", + "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#governance-v3-contracts", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "Holders control implementation upgrades for all v3 market deployments.", + "evidence": [ + { + "name": "Upgrade Documentation", + "summary": "Pool (upgradeable proxy) β†’ Admin is PoolAddressProvider β†’ onlyOwner is Executor β†’ owner is PayloadsController β†’ controlled by Governance.", + "urls": [ + { + "name": "Upgradability per contract", + "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#contracts-upgradeability", + "type": "github" + } + ] + }, + { + "name": "Ownership Chain", + "summary": "Pool β†’ PoolAddressProvider β†’ Executor β†’ PayloadsController β†’ Governance.\n\nFor PayloadsController to call Executor, MESSAGE_ORIGINATOR must equal Governance.", + "urls": [ + { + "name": "Pool", + "url": "https://etherscan.io/address/0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2#code", + "type": "explorer" + }, + { + "name": "Executor", + "url": "https://etherscan.io/address/0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A#code", + "type": "explorer" + }, + { + "name": "PayloadsController", + "url": "https://etherscan.io/address/0xdAbad81aF85554E9ae636395611C58F7eC1aAEc5#code", + "type": "explorer" + }, + { + "name": "Governance", + "url": "https://etherscan.io/address/0x9AEE0B04504CeF83A65AC3f0e838D0593BCb2BC7#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "The AAVE token is upgradeable but controlled by token holders.", + "evidence": [ + { + "name": "Ownership Chain", + "summary": "ERC-1967 Transparent proxy owned by ProxyAdmin, controlled by token holders.\n\nAAVE (Proxy) β†’ ProxyAdmin β†’ Executor β†’ PayloadsController β†’ Token Holders", + "urls": [ + { + "name": "AAVE Proxy", + "url": "https://etherscan.io/address/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9#readProxyContract", + "type": "explorer" + }, + { + "name": "ProxyAdmin", + "url": "https://etherscan.io/address/0x86c3FfEE349A7cFf7cA88C449717B1b133bfb517#code", + "type": "explorer" + }, + { + "name": "PayloadsController", + "url": "https://etherscan.io/address/0xdAbad81aF85554E9ae636395611C58F7eC1aAEc5#readProxyContract", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "Fixed 16m AAVE token supply. No mint() function or inflation pathway in the bytecode.", + "evidence": [ + { + "urls": [ + { + "name": "The only AAVE token minting transactions ever, amounting to 16m AAVE tokens", + "url": "https://etherscan.io/advanced-filter?tkn=0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9&txntype=2&fadd=0x0000000000000000000000000000000000000000", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "positive", + "notes": "Roles can be broadly classified into **Core Protocol Roles** and **Delegated Steward Roles**. AAVE holders elect and revoke all these roles with their standard governance procedure.", + "evidence": [ + { + "name": "Core", + "summary": "Core Protocol Roles: EMERGENCY_ADMIN, RISK_ADMIN, POOL_ADMIN, ASSET_LISTING_ADMIN. These live in ACLManager, owned by ACLAdmin (verifiable in PoolAddressesProvider). ACLAdmin is owned by PayloadsController, controlled by token holders.", + "urls": [ + { + "name": "Core Protocol Role addresses", + "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#admins", + "type": "github" + }, + { + "name": "Core Protocol Roles in ACLManager", + "url": "https://etherscan.io/address/0xc2aaCf6553D20d1e9d78E365AAba8032af9c85b0", + "type": "explorer" + }, + { + "name": "EMERGENCY_ADMIN_ROLE: GnosisSafeProxy", + "url": "https://etherscan.io/address/0x2cfe3ec4d5a6811f4b8067f0de7e47dfa938aa30#code", + "type": "explorer" + }, + { + "name": "POOL_ADMIN_ROLE: Executor", + "url": "https://etherscan.io/address/0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A#code", + "type": "explorer" + } + ] + }, + { + "name": "Delegated Steward Roles", + "summary": "\n\nRisk Stewards β†’ Risk Parameters\nFinance Stewards β†’ Treasury\nAave Guardians β†’ Emergency", + "urls": [ + { + "name": "Stewards (docs)", + "url": "https://aave.com/help/governance/aave-community", + "type": "docs" + }, + { + "name": "Stewards (addresses)", + "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#contracts-upgradeability", + "type": "github" + } + ] + }, + { + "name": "Protocol Emergency Guardian", + "summary": "A separate multisig that executes limited emergency operations for the protocol.", + "urls": [ + { + "name": "Protocol Emergency Guardian", + "url": "https://aave.com/docs/ecosystem/governance#community-guardians-protocol-emergency-guardian", + "type": "docs" + }, + { + "name": "Detailed protocol permissions", + "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#contracts", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "No Guardian or blacklist capabilities exist in the AAVE token contract.", + "evidence": [ + { + "urls": [ + { + "name": "AAVE token's implementation code", + "url": "https://etherscan.io/address/0x5d4aa78b08bc7c530e21bf7447988b1be7991322#code", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "Protocol fees flow to a governance-controlled treasury. Holders can stake as stkAAVE in Safety Module for yield. Treasury composed of reserve factor from borrower interest + liquidation fees + flashloan premiums.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "Alongside the protocol fees going to the AAVE token-holder controlled treasury as an important source of value accrual, holders can stake their AAVE tokens as stkAAVE in Aave's Safety module, getting additional AAVE tokens as yield from the treasury for bearing the risk of slashing in case of protocol insolvency.\n\n**Caveat:** This Safety Module is in the transitional phase of being sunset in favour of aToken staking as part of the Umbrella release for automated bad debt coverage, which is live already.", + "evidence": [ + { + "urls": [ + { + "name": "Safety Module - Incentives", + "url": "https://aave.com/docs/aave-v3/concepts/incentives#safety-module", + "type": "docs" + }, + { + "name": "Umbrella Transition", + "url": "https://aave.com/help/umbrella/stake", + "type": "docs" + }, + { + "name": "Umbrella Activation Proposal", + "url": "https://vote.onaave.com/proposal/?proposalId=320", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "positive", + "notes": "AAVE holders control treasury usage. Composed of reserve factor (borrower interest), liquidation fees, and flashloan premiums.", + "evidence": [ + { + "name": "Treasury Control", + "summary": "Token holders direct treasury via governance.", + "urls": [ + { + "name": "Treasury docs", + "url": "https://aave.com/docs/aave-v3/concepts/incentives#incentives", + "type": "docs" + } + ] + }, + { + "name": "Fee Sources", + "summary": "Reserve factor (interest), liquidation fees, flashloan premiums all route to treasury via mintToTreasury().", + "urls": [ + { + "name": "mintToTreasury (PoolLogic)", + "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/libraries/logic/PoolLogic.sol#L105", + "type": "github" + }, + { + "name": "Flashloan premium", + "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/pool/Pool.sol#L653", + "type": "github" + }, + { + "name": "Liquidation fee", + "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/libraries/logic/LiquidationLogic.sol#L392", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "Token holders control fee levers (Reserve Factor, liquidation fees) via ACL-gated admin roles. Emergency actions are delegated, but economic controls are governance-owned through ACLManager.", + "evidence": [ + { + "name": "ACL Control", + "summary": "ACLManager gates admin roles (POOL_ADMIN, RISK_ADMIN) which control fee parameters. ACLManager itself is controlled by governance.", + "urls": [ + { + "name": "POOL_ADMIN role", + "url": "https://aave.com/docs/aave-v3/smart-contracts/acl-manager#roles-pool-admin", + "type": "docs" + }, + { + "name": "ACLManager", + "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/configuration/ACLManager.sol#L45", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "Aragon has not verified additional offchain value accrual flows to the AAVE token", + "tags": [ + "Reference" + ], + "evidence": [] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "AAVE token proxy and implementation are verified on Etherscan. Aave V3 protocol contracts are open source and verified.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The AAVE token contract source code is publicly available and verified on Etherscan.", + "evidence": [ + { + "urls": [ + { + "name": "AAVE token", + "url": "https://etherscan.io/address/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9#readProxyContract", + "type": "explorer" + }, + { + "name": "Aave V3 Origin (GitHub)", + "url": "https://github.com/aave-dao/aave-v3-origin", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "Aave V3 protocol contracts are open source on GitHub and verified on Etherscan.", + "evidence": [ + { + "urls": [ + { + "name": "Aave V3 Origin (GitHub)", + "url": "https://github.com/aave-dao/aave-v3-origin", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "Aragon has not verified if any 3rd party controls a majority share of the voting power of AAVE or derivatives.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "unevaluated", + "notes": "Aragon has not yet verified if any 3rd party controls a majority share of the voting power of AAVE or derivatives.", + "evidence": [] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "unevaluated", + "notes": "Aragon has not yet verified the supply schedule criteria for the AAVE token", + "evidence": [] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "US AAVE trademark is held by Quantum Swan OU (Estonia). Primary interface terms identify Aave Labs (a separate entity from the Aave DAO) as the contracting party.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "A US AAVE trademark filing lists Quantum Swan OU (Estonia) as the applicant/owner.", + "evidence": [ + { + "urls": [ + { + "name": "Trademark filing", + "url": "https://tsdr.uspto.gov/#caseNumber=79251290&caseSearchType=US_APPLICATION&caseType=DEFAULT&searchType=statusSearch", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "The primary interface terms identify Aave Labs as the contracting party.", + "evidence": [ + { + "urls": [ + { + "name": "Terms of Service", + "url": "https://aave.com/terms-of-service", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "unevaluated", + "notes": "Aragon has not yet verified all the licensing criteria associated with the AAVE token or the corresponding protocol and assets", + "evidence": [] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/aero.json b/src/data/generated/tokens/aero.json new file mode 100644 index 0000000..640852d --- /dev/null +++ b/src/data/generated/tokens/aero.json @@ -0,0 +1,658 @@ +{ + "id": "aero", + "coingeckoId": "aerodrome-finance", + "name": "AERO", + "symbol": "AERO", + "address": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", + "icon": "https://assets.coingecko.com/coins/images/31745/standard/token.png", + "description": "AERO is the native token of the Aerodrome protocol, a ve(3,3) MetaDex on Base with largely immutable contracts and programmatic value flows to veAERO holders.", + "network": "base", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://aerodrome.finance/", + "twitter": "https://twitter.com/Aerodrome", + "scan": "https://basescan.org/token/0x940181a94A35A4569E4529A3CDfB74e38FD98631" + }, + "infoDescription": "Aerodrome is a decentralized exchange where you can execute low-fee swaps, deposit tokens to earn rewards, and actively participate in the onchain economy.", + "positive": 12, + "neutral": 3, + "atRisk": 0, + "evidenceEntries": 23, + "score": { + "tokenId": "aero", + "passing": 12, + "total": 13, + "percentage": 92.3076923076923, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 6, + "total": 7, + "percentage": 85.71428571428571, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "warning", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "Aerodrome's core contracts are largely immutable with control of emissions given to veAERO holders via the gauge voting system. There are some minor areas in which privileged parties can control specific areas of the protocol, which are detailed below.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "veAERO holders control emissions through epoch-based gauge voting. An EmergencyCouncil exists for crisis handling with limited emergency powers.", + "evidence": [ + { + "name": "Voting System", + "summary": "AERO holders lock AERO in VotingEscrow to receive veAERO. veAERO holders vote each epoch on gauge weights via the Voter contract, and the Minter distributes emissions proportionally.", + "urls": [ + { + "name": "AERO Token", + "url": "https://basescan.org/address/0x940181a94A35A4569E4529A3CDfB74e38FD98631#code", + "type": "explorer" + }, + { + "name": "VotingEscrow", + "url": "https://basescan.org/address/0xeBf418Fe2512e7E6bd9b87a8F0f294aCDC67e6B4#code", + "type": "explorer" + }, + { + "name": "Voter", + "url": "https://basescan.org/address/0x16613524e02ad97eDfeF371bC883F2F5d6C480A5#code", + "type": "explorer" + }, + { + "name": "Minter", + "url": "https://basescan.org/address/0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5#code", + "type": "explorer" + } + ] + }, + { + "name": "Emergency Council", + "summary": "Exists to handle contingencies. It can kill or revive gauges, update pool name and symbol metadata, and rotate the EmergencyCouncil itself.", + "urls": [ + { + "name": "EmergencyCouncil", + "url": "https://aerodrome.limited/pages/security.html", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "veAERO holders indirectly control AERO emissions through epoch-based gauge voting executed by the Voter and Minter.", + "evidence": [ + { + "name": "Emergency Council", + "summary": "Exists but is not elected by veAERO holders. It holds limited emergency powers, including killing or reviving gauges and managing pool metadata. This role exists explicitly for crisis handling and sits outside direct tokenholder election.", + "urls": [ + { + "name": "Voter: emergencyCouncil", + "url": "https://basescan.org/address/0x16613524e02ad97eDfeF371bC883F2F5d6C480A5#readContract", + "type": "explorer" + }, + { + "name": "EmergencyCouncil", + "url": "https://basescan.org/address/0x99249b10593fCa1Ae9DAE6D4819F1A6dae5C013D#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "Core Aerodrome protocol contracts are non-upgradeable and do not use proxy patterns.", + "evidence": [ + { + "urls": [ + { + "name": "Aerodrome Contract Addresses", + "url": "https://aerodrome.finance/security", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "The AERO token contract is immutable and does not use a proxy pattern. The Minter contract, which is authorized to mint AERO, is also non-upgradeable.", + "evidence": [ + { + "urls": [ + { + "name": "AERO Token", + "url": "https://basescan.org/address/0x940181a94A35A4569E4529A3CDfB74e38FD98631#code", + "type": "explorer" + }, + { + "name": "Minter", + "url": "https://basescan.org/address/0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "AERO minting is fully programmatic and epoch-based (1 epoch = 1 week), enforced by the non-upgradeable Minter contract.", + "evidence": [ + { + "name": "Minter Contract", + "summary": "The non-upgradeable Minter contract enforces programmatic emission rules.", + "urls": [ + { + "name": "Minter", + "url": "https://basescan.org/address/0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5#code", + "type": "explorer" + } + ] + }, + { + "name": "Emission Rate", + "summary": "Can be increased/decreased by governance but only in fixed, small, weekly increments of +/-0.01%.", + "urls": [ + { + "name": "Minter Emission Increase (Epochs 1-14)", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L182", + "type": "github" + }, + { + "name": "Minter Emission Decay", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L184", + "type": "github" + } + ] + }, + { + "name": "Growth Emissions", + "summary": "Mints growth emissions for veAERO holders using the formula: weeklyEmissions * (1 - veAERO.totalSupply / AERO.totalSupply)^2 * 0.5. This creates a counter-cyclical incentive where veAERO rewards decrease as more AERO is locked.", + "urls": [ + { + "name": "Growth Emissions Formula", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L135", + "type": "github" + } + ] + }, + { + "name": "Team Emission Rate", + "summary": "Currently set to 0% of total weekly emissions.", + "urls": [ + { + "name": "Team Emission Rate", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L192", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "warning", + "notes": "The feeManager role exists which is controlled by a Safe multisig, not veAERO holders. This role can modify fee parameters across both pool types.", + "evidence": [ + { + "name": "Fee Manager Role", + "summary": "The feeManager role is controlled by a Safe multisig and can modify fee parameters.", + "urls": [ + { + "name": "Safe (Fee Manager)", + "url": "https://basescan.org/address/0xE6A41fE61E7a1996B59d508661e3f524d6A32075#code", + "type": "explorer" + } + ] + }, + { + "name": "Non-SlipStream Fees", + "summary": "Fees are set on the PoolFactory which can be changed by the feeManager.", + "urls": [ + { + "name": "PoolFactory: FeeManager", + "url": "https://basescan.org/address/0x420DD381b31aEf6683db6B902084cB0FFECe40Da#code", + "type": "explorer" + } + ] + }, + { + "name": "SlipStream Fees", + "summary": "For CLPoolFactory, fees are set inside the SwapFeeModule which allows feeManager to change fees.", + "urls": [ + { + "name": "SlipStream: CLFactory (uses SwapFeeModule for fees)", + "url": "https://basescan.org/address/0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A#code", + "type": "explorer" + }, + { + "name": "SwapFeeModule: setCustomFee", + "url": "https://basescan.org/address/0xF4171B0953b52Fa55462E4d76ecA1845Db69af00#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "The AERO token has no blacklist, pause, or seizure mechanisms.", + "evidence": [ + { + "name": "Team Rate", + "summary": "The team rate (share of weekly emissions allocated to the team) can be modified by a Safe controlled by the Aerodrome team.", + "urls": [ + { + "name": "Safe (Team)", + "url": "https://basescan.org/address/0xBDE0c70BdC242577c52dFAD53389F82fd149EA5a#code", + "type": "explorer" + }, + { + "name": "Team Rate Change", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L129", + "type": "github" + } + ] + }, + { + "name": "Emission Rate", + "summary": "Changes to the weekly emission rate (+/-0.01% or unchanged) are controlled by Governor (a multisig), soon to be handed over to governance contracts controlled by veAERO holders.", + "urls": [ + { + "name": "Governor (Safe)", + "url": "https://basescan.org/address/0xE6A41fE61E7a1996B59d508661e3f524d6A32075", + "type": "explorer" + }, + { + "name": "Emission Rate Change", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L145", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "veAERO holders receive growth emissions and trading fees from staked LP positions. There is no protocol treasury - all trading fees are distributed. Offchain brand controlled by Aerodrome Foundation.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "veAERO holders receive rewards from growth emissions and trading fees from staked LP positions.", + "evidence": [ + { + "name": "Growth Emissions", + "summary": "veAERO holders receive rewards from growth emissions transferred to RewardDistributor, claimable in proportion to ve balance.", + "urls": [ + { + "name": "Growth Transfer to RewardDistributor", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L201", + "type": "github" + }, + { + "name": "RewardsDistributor Claim", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/RewardsDistributor.sol#L126", + "type": "github" + } + ] + }, + { + "name": "Non-Slipstream Trading Fees", + "summary": "For normal pools, swap fees from LPs who have staked their LP tokens in gauges go to veAERO holders.", + "urls": [ + { + "name": "Gauge Deposit Logic", + "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/gauges/Gauge.sol#L155", + "type": "github" + }, + { + "name": "Swap Fee Accrued", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Pool.sol#L378", + "type": "github" + }, + { + "name": "Swap Fee Claimed", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Pool.sol#L143", + "type": "github" + }, + { + "name": "Voter calling Gauge to claim fees", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Voter.sol#L492", + "type": "github" + }, + { + "name": "Swap Fee Claimed by Gauge on PoolFees", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/gauges/Gauge.sol#L82", + "type": "github" + } + ] + }, + { + "name": "Slipstream Trading Fees", + "summary": "For Slipstream (CL) pools, swap fees from staked LP positions go to veAERO holders.", + "urls": [ + { + "name": "Fees Split", + "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/core/CLPool.sol#L844", + "type": "github" + }, + { + "name": "Gauge Fees calculated separately", + "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/core/CLPool.sol#L844C37-L844C46", + "type": "github" + }, + { + "name": "Non Staked LP Fees collected", + "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/core/CLPool.sol#L483", + "type": "github" + }, + { + "name": "Gauge Fees to feesVotingReward", + "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/gauge/CLGauge.sol#L340", + "type": "github" + }, + { + "name": "veAERO voters claim fees", + "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/rewards/Reward.sol#L225", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "positive", + "notes": "There is no protocol treasury. All trading fees from swapping pools are distributed to veAERO holders and LPs.", + "evidence": [] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "veAERO holders control:\n\n1. The AERO rewards they receive by locking AERO tokens in VotingEscrow\n\n2. The AERO emissions going into gauges by voting for them in Voter\n\n**Note:** Fee parameters are controlled by the feeManager (a Safe multisig), not by veAERO holders directly.", + "evidence": [ + { + "name": "Locking Control", + "summary": "veAERO holders control their rewards by locking AERO in VotingEscrow.", + "urls": [ + { + "name": "VotingEscrow create_lock", + "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/VotingEscrow.sol#L555", + "type": "github" + } + ] + }, + { + "name": "Gauge Voting", + "summary": "veAERO holders direct emissions by voting for gauges in the Voter contract.", + "urls": [ + { + "name": "Voter vote function", + "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/Voter.sol#L249", + "type": "github" + }, + { + "name": "Gauge getReward", + "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/gauges/Gauge.sol#L179", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "Aragon has not verified additional offchain value accrual flows to the AERO token", + "evidence": [] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "AERO token source is publicly available on GitHub and verified on Basescan. Aerodrome protocol contracts are open source and verified.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The AERO token contract source code (Aero.sol) is publicly available on GitHub and verified on Basescan.", + "evidence": [ + { + "urls": [ + { + "name": "AERO Token (Basescan)", + "url": "https://basescan.org/address/0x940181a94A35A4569E4529A3CDfB74e38FD98631#code", + "type": "explorer" + }, + { + "name": "Aero.sol Source (GitHub)", + "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/Aero.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "Aerodrome protocol contracts (including Slipstream) are open source on GitHub.", + "evidence": [ + { + "urls": [ + { + "name": "Aerodrome Contracts (GitHub)", + "url": "https://github.com/aerodrome-finance/contracts", + "type": "github" + }, + { + "name": "Aerodrome Slipstream (GitHub)", + "url": "https://github.com/aerodrome-finance/slipstream", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "Distribution of AERO and veAERO voting power outside of the core team has not been independently verified.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "unevaluated", + "notes": "Distribution of AERO and veAERO voting power outside of the core team has not been independently verified.", + "evidence": [] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "positive", + "notes": "Future AERO unlocks are determined by the supply schedule and the veAERO system detailed above. Aragon is not aware of any significant vesting cliffs.", + "evidence": [] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "US AERODROME trademark is held by Perpetual Cyclist Services LLC (Delaware). Primary interface terms identify the Aerodrome Foundation as the contracting party.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "A US AERODROME trademark filing lists Perpetual Cyclist Services LLC (Delaware) as the applicant/owner.", + "evidence": [ + { + "urls": [ + { + "name": "USPTO Report", + "url": "https://uspto.report/TM/99083103", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "The primary interface terms identify the Aerodrome Foundation as the contracting party.", + "evidence": [ + { + "urls": [ + { + "name": "Aerodrome Legal", + "url": "https://aero.drome.eth.link/legal", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "unevaluated", + "notes": "Aragon has not yet verified all the licensing criteria associated with the AERO token or the corresponding protocol and assets", + "evidence": [] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/crv.json b/src/data/generated/tokens/crv.json new file mode 100644 index 0000000..af77c8f --- /dev/null +++ b/src/data/generated/tokens/crv.json @@ -0,0 +1,584 @@ +{ + "id": "crv", + "coingeckoId": "curve-dao-token", + "name": "CRV", + "symbol": "CRV", + "address": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "icon": "https://assets.coingecko.com/coins/images/12124/standard/Curve.png", + "description": "CRV is the native token of the Curve protocol - a leading stablecoin DEX. The veCRV gauge system enables programmatic, onchain value direction by tokenholders.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://curve.finance/", + "twitter": "https://twitter.com/curvefinance", + "scan": "https://etherscan.io/token/0xD533a949740bb3306d119CC777fa900bA034cd52" + }, + "infoDescription": "Curve is a decentralized exchange (DEX) and automated market maker (AMM) on Ethereum and EVM-compatible sidechains/L2s, designed for the efficient trading of stablecoins and volatile assets.", + "positive": 13, + "neutral": 2, + "atRisk": 0, + "evidenceEntries": 23, + "score": { + "tokenId": "crv", + "passing": 13, + "total": 13, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "veCRV holders control all protocol operations through Aragon v1's Voting Contract and Agent. The CRV token is immutable with programmatic inflation and no censorship capabilities. Core protocol and pool contracts are immutable.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "Governance is enforced through Aragon v1's Voting Contract, which authorizes execution via the Agent contract. The Agent contract is set as the owner for all protocol-gated functions, ensuring veCRV holders control all operations.", + "evidence": [ + { + "urls": [ + { + "name": "Aragon Voting Contract", + "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356", + "type": "explorer" + }, + { + "name": "Agent Contract", + "url": "https://etherscan.io/address/0x40907540d8a6C65c637785e8f8B742ae6b0b9968", + "type": "explorer" + }, + { + "name": "CRV Token: Admin is set to Agent", + "url": "https://etherscan.io/token/0xd533a949740bb3306d119cc777fa900ba034cd52#readContract", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "All protocol roles are controlled by the Agent contract, ensuring veCRV holders maintain control over gauges, fees, and protocol expansion.", + "evidence": [ + { + "name": "Gauge Addition", + "summary": "Only the Agent can add new gauges via add_gauge on GaugeController.", + "urls": [ + { + "name": "GaugeController: admin is Agent", + "url": "https://etherscan.io/address/0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB#readContract", + "type": "explorer" + } + ] + }, + { + "name": "Pool Factory Ownership", + "summary": "New pools are deployed by Factory contracts controlled by OwnerProxy. Agent is responsible for adding new pools, giving veCRV holders control over protocol expansion.", + "urls": [ + { + "name": "Curve Pool Factory: admin is OwnerProxy", + "url": "https://etherscan.io/address/0xB9fc157394Af804a3578134A6585C0dC9cc990d4#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "Core protocol and pool contracts are immutable. Governance contracts (Agent, Voting) are upgradeable only by veCRV holders.", + "evidence": [ + { + "name": "Core Contracts", + "summary": "Core protocol and pool contracts are immutable. The CRV Token and GaugeController have admin set to Agent but cannot be upgraded.", + "urls": [ + { + "name": "CRV Token (Admin: Agent)", + "url": "https://etherscan.io/address/0xd533a949740bb3306d119cc777fa900ba034cd52", + "type": "explorer" + }, + { + "name": "GaugeController (Admin: Agent)", + "url": "https://etherscan.io/address/0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB#readContract", + "type": "explorer" + } + ] + }, + { + "name": "Agent Contract", + "summary": "Can be upgraded by calling setApp(Agent, ...) on the Kernel contract, which is only allowed by the Agent itself. Only veCRV holders can upgrade the Agent contract through governance voting.", + "urls": [ + { + "name": "Agent", + "url": "https://etherscan.io/address/0x40907540d8a6C65c637785e8f8B742ae6b0b9968#code", + "type": "explorer" + } + ] + }, + { + "name": "Voting Contract", + "summary": "Can be upgraded by calling setApp(Voting, ...) on the Kernel contract, which is only allowed by the Agent itself. Only veCRV holders can upgrade the Voting contract through governance voting.", + "urls": [ + { + "name": "Voting", + "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "The CRV token contract is immutable with no proxy patterns or upgrade mechanisms.", + "evidence": [ + { + "urls": [ + { + "name": "ERC20CRV.vy Source", + "url": "https://etherscan.io/token/0xd533a949740bb3306d119cc777fa900ba034cd52#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "CRV has a programmatic inflation schedule with yearly epochs and decreasing emission rate. All minting flows through the Minter contract based on gauge participation.", + "evidence": [ + { + "name": "Supply Schedule", + "summary": "CRV inflation is released in yearly epochs: each year allows a fixed maximum amount to be minted linearly over time, and at the end of the year the minting rate is reduced by a factor of 2^(1/4). Any unminted supply from a year is permanently lost.", + "urls": [ + { + "name": "CRV Emission Schedule (Docs)", + "url": "https://docs.curve.finance/user/curve-tokens/crv#emission-schedule", + "type": "docs" + }, + { + "name": "ERC20CRV.vy Yearly Epoch Logic", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/ERC20CRV.vy#L115C40-L115C59", + "type": "github" + } + ] + }, + { + "name": "Minting Access", + "summary": "Mint can only occur through the Minter contract which validates gauge participation. Amount minted depends on veCRV balance and provided LP tokens in a gauge.", + "urls": [ + { + "name": "ERC20CRV.vy Mint Access", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/ERC20CRV.vy#L333", + "type": "github" + }, + { + "name": "Minter.vy Gauge Validation", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/Minter.vy#L46", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "positive", + "notes": "Core protocol functions are either permissionless or gated by Agent contract (hence veCRV holders). For gate control related to fees/revenues, see accrual section.", + "evidence": [ + { + "name": "Add/Kill Gauge", + "summary": "To add a new gauge in GaugeController, veCRV holders must vote in majority. The admin on GaugeController is set to the Aragon Agent Contract, which sits as the final step in the governance process. Governance starts with the Aragon Voting contract that uses veCRV token for voting. veCRV holders can also kill a gauge, permanently setting its rewards to 0.", + "urls": [ + { + "name": "Gauge Kill Implementation", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/gauges/LiquidityGaugeV5.vy#L731", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "The CRV token has no transfer restrictions, blacklist functionality, or pausable transfers. There's Admin set (Agent) on the CRV token, but it can only update token's name and symbol.", + "evidence": [ + { + "urls": [ + { + "name": "ERC20CRV.vy Standard Transfer", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/ERC20CRV.vy#L272", + "type": "github" + }, + { + "name": "ERC20CRV.vy Metadata Logic", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/ERC20CRV.vy#L365", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "veCRV holders receive 50% of all trading fees distributed as crvUSD rewards. Value flows are programmatic through the gauge system. Offchain structure shows Swiss Stake AG controls the interface and trademark.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "veCRV holders receive 50% of all trading fees distributed as crvUSD rewards, plus boosted CRV emissions for liquidity provision. They also receive 80% of the accrued interest from all crvUSD markets.", + "evidence": [ + { + "name": "CRV Emissions Rewards", + "summary": "Liquidity Providers deposit LP tokens into Gauge Contracts. Once the gauge receives CRV emissions, LPs can claim proportional rewards. LPs can boost rewards up to 2.5x by locking CRV for veCRV.", + "urls": [ + { + "name": "working_balance reward calculation", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/gauges/LiquidityGaugeV5.vy#L210", + "type": "github" + } + ] + }, + { + "name": "Pool Rewards in crvUSD", + "summary": "Pools have swap fees with an admin portion collected by StableSwapProxy. Fees flow through burner contracts to FeeCollector, which converts all tokens to crvUSD via CowSwapBurner and sends to FeeDistributor for veCRV holders to claim.", + "urls": [ + { + "name": "FeeDistributor", + "url": "https://etherscan.io/address/0xD16d5eC345Dd86Fb63C6a9C43c517210F1027914", + "type": "explorer" + } + ] + }, + { + "name": "Borrow Rewards in crvUSD", + "summary": "Borrowing interest (paid in crvUSD) from controllers are sent to FeeSplitter. FeeCollector receives proportional crvUSD and sends to FeeDistributor for veCRV holders.", + "urls": [ + { + "name": "FeeSplitter", + "url": "https://etherscan.io/address/0x2dfd89449faff8a532790667bab21cf733c064f2", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "positive", + "notes": "50% of all trading fees are distributed to veCRV holders through crvUSD rewards, while the remaining 50% goes to the respective liquidity providers of the pools. Only veCRV holders can change this behaviour, hence Curve has no separate treasury. All revenue to the people.", + "evidence": [ + { + "urls": [ + { + "name": "veCRV Revenue Share (Docs)", + "url": "https://docs.curve.finance/user/vecrv/revenue", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "Gauge weights are determined programmatically by veCRV votes, not discretionary decisions.", + "evidence": [ + { + "name": "Pool Fee Control", + "summary": "Pool fee changes are executed via commit_new_fee, which can only be called by the pool owner (StableSwapProxy) which in turn is restricted to parameter_admin (Agent contract).", + "urls": [ + { + "name": "DAI/USDC/USDT Pool: commit_new_fee", + "url": "https://etherscan.io/address/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7#code", + "type": "explorer" + } + ] + }, + { + "name": "Burner Attachment", + "summary": "set_burner on StableSwapProxy is gated by ownership_admin (Agent contract).", + "urls": [ + { + "name": "StableSwapProxy: set_burner", + "url": "https://etherscan.io/address/0xeCb456EA5365865EbAb8a2661B0c503410e9B347#code", + "type": "explorer" + } + ] + }, + { + "name": "Burn Execution", + "summary": "The burn function invokes each coin's attached burner contract. Can be disabled by either the Agent contract or the emergency Safe multisig.", + "urls": [ + { + "name": "StableSwapProxy: burn", + "url": "https://etherscan.io/address/0xeCb456EA5365865EbAb8a2661B0c503410e9B347#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "Aragon has not verified additional offchain value accrual flows to the CRV token", + "evidence": [] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "CRV token source (Vyper) is publicly available on GitHub and verified on Etherscan. Curve DAO contracts are open source and verified.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The CRV token contract source code (ERC20CRV.vy) is publicly available on GitHub and verified on Etherscan.", + "evidence": [ + { + "urls": [ + { + "name": "CRV Token (Etherscan)", + "url": "https://etherscan.io/token/0xd533a949740bb3306d119cc777fa900ba034cd52#code", + "type": "explorer" + }, + { + "name": "ERC20CRV.vy Source (GitHub)", + "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/ERC20CRV.vy", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "Curve DAO protocol contracts are open source on GitHub.", + "evidence": [ + { + "urls": [ + { + "name": "Curve DAO Contracts (GitHub)", + "url": "https://github.com/curvefi/curve-dao-contracts", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "Aragon has not verified if the majority of veCRV voting power lies with a single party.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "unevaluated", + "notes": "Aragon has not yet verified if the majority of veCRV voting power lies with a single party", + "evidence": [] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "positive", + "notes": "Future CRV unlocks are determined by the programmatic supply schedule and veCRV system. Emissions decrease yearly by 2^(1/4). Aragon is not aware of any significant vesting cliffs.", + "evidence": [ + { + "urls": [ + { + "name": "CRV Emission Schedule (Docs)", + "url": "https://docs.curve.finance/user/curve-tokens/crv#emission-schedule", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Swiss CRV trademark is held by Swiss Stake AG. Curve interface terms identify Swiss Stake AG (Zug, Switzerland) as the contracting party.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "The Swiss CRV trademark registration lists Swiss Stake AG as the owner.", + "evidence": [ + { + "urls": [ + { + "name": "Swiss Trademark Registry", + "url": "https://www.swissreg.ch/database-client/register/detail?lang=de&no=16098%2F2020&type=trademark", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "The Curve interface terms identify Swiss Stake AG (Zug, Switzerland) as the contracting party.", + "evidence": [ + { + "urls": [ + { + "name": "Curve Legal", + "url": "https://www.curve.finance/dex/ethereum/legal", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "unevaluated", + "notes": "Aragon has not yet verified all the licensing criteria associated with the CRV token or the corresponding protocol and assets", + "evidence": [] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/ena.json b/src/data/generated/tokens/ena.json new file mode 100644 index 0000000..1b44d42 --- /dev/null +++ b/src/data/generated/tokens/ena.json @@ -0,0 +1,840 @@ +{ + "id": "ena", + "coingeckoId": "ethena", + "name": "ENA", + "symbol": "ENA", + "address": "0x57e114B691Db790C35207b2e685D4A43181e6061", + "icon": "https://assets.coingecko.com/coins/images/36530/standard/ethena.png", + "description": "ENA is the governance token of Ethena, a synthetic dollar protocol. Governance is advisory via Snapshot signaling; the Dev Multisig executes all decisions. Fee switch received positive forum signals but awaits Snapshot vote and onchain execution.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://ethena.fi/", + "twitter": "https://twitter.com/ethena", + "scan": "https://etherscan.io/token/0x57e114B691Db790C35207b2e685D4A43181e6061" + }, + "infoDescription": "Ethena is a synthetic dollar protocol built on Ethereum, offering a crypto-native alternative to traditional stablecoins via its USDe token and yield-bearing sUSDe.", + "positive": 2, + "neutral": 16, + "atRisk": 0, + "evidenceEntries": 29, + "score": { + "tokenId": "ena", + "passing": 2, + "total": 15, + "percentage": 13.333333333333334, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 0, + "total": 7, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 0, + "total": 4, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "warning", + "onchain-ctrl__role-accountability": "warning", + "onchain-ctrl__protocol-upgrade": "warning", + "onchain-ctrl__token-upgrade": "warning", + "onchain-ctrl__supply": "warning", + "onchain-ctrl__access-gating": "warning", + "onchain-ctrl__censorship": "warning", + "val-accrual__active": "warning", + "val-accrual__treasury": "warning", + "val-accrual__mechanism": "warning", + "val-accrual__offchain": "warning", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "warning", + "distribution__supply-schedule": "warning", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "ENA governance is advisory only. Tokenholders vote via Snapshot, but the Dev Multisig executes all decisions. There is no onchain governance workflow, no timelock, and ENA holders cannot elect or remove multisig signers. The ENA token itself is non-upgradeable, but sENA and rsENA are upgradeable proxies controlled by separate multisigs. Staking contracts include blacklist capabilities with seizure powers.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "warning", + "notes": "ENA governance is **offchain Snapshot signaling only**. Votes do not trigger onchain transactions. The Dev Multisig decides whether to execute proposals, making tokenholder votes advisory rather than binding. There is no timelock on multisig actions.", + "evidence": [ + { + "name": "Snapshot Governance", + "summary": "ENA holders vote via Snapshot at ethenagovernance.eth. All passed votes require manual multisig execution. Per docs: \"fully on-chain governance is not a practical or viable option at present.\"", + "urls": [ + { + "name": "Snapshot Space", + "url": "https://snapshot.org/#/ethenagovernance.eth", + "type": "docs" + }, + { + "name": "Governance Documentation", + "url": "https://docs.ethena.fi/solution-overview/governance", + "type": "docs" + } + ] + }, + { + "name": "Multisig Execution", + "summary": "The Dev Multisig owns all core contracts. Verified onchain: `getThreshold()` returns 5, `getOwners()` returns 11 addresses. Documentation claims 4/8, but onchain reality is 5/11.", + "urls": [ + { + "name": "Dev Multisig", + "url": "https://etherscan.io/address/0x3b0aaf6e6fcd4a7ceef8c92c32dfea9e64dc1862", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "warning", + "notes": "All privileged roles are controlled by Ethena Labs multisigs or EOAs, **NOT** by ENA tokenholders. The Risk Committee is elected via Snapshot but has no onchain authority. No mechanism exists for ENA holders to remove or replace multisig signers.", + "evidence": [ + { + "name": "Multisig Control Matrix", + "summary": "**Dev Multisig:** All contract ownership, upgrades, minting, parameter changes. Signers NOT elected by ENA holders.\n\n**Hot Swap:** Protocol revenue flow, USDe conversion. Signers NOT elected.\n\n**sUSDe Payout:** Staker reward distribution. Signers NOT elected.\n\n**Trading Operations:** Onchain operational activities. Signers NOT elected.\n\n**Reserve Fund:** Emergency reserve deployment. Signers NOT elected.", + "urls": [ + { + "name": "Multisig Matrix Documentation", + "url": "https://docs.ethena.fi/solution-design/key-trust-assumptions/matrix-of-multisig-and-timelocks", + "type": "docs" + }, + { + "name": "Dev Multisig", + "url": "https://etherscan.io/address/0x3b0aaf6e6fcd4a7ceef8c92c32dfea9e64dc1862", + "type": "explorer" + }, + { + "name": "Hot Swap Multisig", + "url": "https://etherscan.io/address/0x4423198f26764a8ce9ac8f1683c476854c885d9d", + "type": "explorer" + }, + { + "name": "sUSDe Payout Multisig", + "url": "https://etherscan.io/address/0x71e4f98e8f20c88112489de3dded4489802a3a87", + "type": "explorer" + }, + { + "name": "Trading Operations Multisig", + "url": "https://etherscan.io/address/0x0a0b96A730ED5CDa84bcB63c1Ee2edCb6B7764d6", + "type": "explorer" + }, + { + "name": "Reserve Fund Multisig", + "url": "https://etherscan.io/address/0x2b5ab59163a6e93b4486f6055d33ca4a115dd4d5", + "type": "explorer" + } + ] + }, + { + "name": "EOA Control Points", + "summary": "**StakingRewardsDistributor Operator** (EOA): Can trigger `transferInRewards()` to distribute USDe to sUSDe stakers.\n\n**Minters** (20 EOAs): Can mint USDe via EthenaMinting.\n\n**Redeemers** (20 EOAs): Can redeem USDe.\n\n**Gatekeepers** (3+ EOAs): Can disable USDe mint/redeem globally.", + "urls": [ + { + "name": "StakingRewardsDistributor Operator (EOA)", + "url": "https://etherscan.io/address/0xe3880B792F6F0f8795CbAACd92E7Ca78F5d3646e", + "type": "explorer" + }, + { + "name": "Multisig Matrix (Minter/Redeemer/Gatekeeper docs)", + "url": "https://docs.ethena.fi/solution-design/key-trust-assumptions/matrix-of-multisig-and-timelocks", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "warning", + "notes": "**sENA is upgradeable** via proxy controlled by Dev Multisig. **rsENA is also upgradeable** but controlled by a different multisig. Neither upgrade path involves tokenholder approval. ENA, USDe, sUSDe, and EthenaMinting are non-upgradeable.", + "evidence": [ + { + "name": "Non-Upgradeable Contracts", + "summary": "ENA, USDe, sUSDe, and EthenaMinting V2 are not proxy contracts.", + "urls": [ + { + "name": "ENA Token", + "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", + "type": "explorer" + }, + { + "name": "USDe Token", + "url": "https://etherscan.io/address/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code", + "type": "explorer" + }, + { + "name": "sUSDe Token", + "url": "https://etherscan.io/address/0x9d39a5de30e57443bff2a8307a4256c8797a3497#code", + "type": "explorer" + }, + { + "name": "EthenaMinting V2", + "url": "https://etherscan.io/address/0xe3490297a08d6fC8Da46Edb7B6142E4F461b62D3#code", + "type": "explorer" + } + ] + }, + { + "name": "Upgradeable Contracts (sENA and rsENA)", + "summary": "**sENA:** Uses EIP-1967 proxy. Dev Multisig can upgrade without tokenholder approval. No timelock.\n\n**rsENA:** Uses EIP-1967 proxy with a different upgrade controller. Controlled by a separate multisig with signers not publicly identified. No timelock.", + "urls": [ + { + "name": "sENA Proxy", + "url": "https://etherscan.io/address/0x8bE3460A480c80728a8C4D7a5D5303c85ba7B3b9#code", + "type": "explorer" + }, + { + "name": "sENA Implementation", + "url": "https://etherscan.io/address/0x7fd57b46ae1a7b14f6940508381877ee03e1018b#code", + "type": "explorer" + }, + { + "name": "sENA ProxyAdmin", + "url": "https://etherscan.io/address/0xf849d7792ff9b30a57656ee10a2776bcb49f4fe4#code", + "type": "explorer" + }, + { + "name": "rsENA Proxy", + "url": "https://etherscan.io/address/0xc65433845ecd16688eda196497fa9130d6c47bd8#code", + "type": "explorer" + }, + { + "name": "rsENA Implementation", + "url": "https://etherscan.io/address/0x09bba67c316e59840699124a8dc0bbda6a2a9d59#code", + "type": "explorer" + }, + { + "name": "rsENA ProxyAdmin", + "url": "https://etherscan.io/address/0xa59b36aca119a30c527eddaa386eb130bcf1939f", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "warning", + "notes": "The ENA token itself is **NOT upgradeable**. It uses Ownable2Step, not a proxy pattern. Token behavior is immutable. However, the owner (Dev Multisig) retains mint authority subject to rate limits.", + "evidence": [ + { + "name": "ENA Non-Upgradeability Verification", + "summary": "The ENA token inherits Ownable2Step, ERC20Burnable, ERC20Permit. No upgrade mechanism exists in the contract.", + "urls": [ + { + "name": "ENA.sol Source", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol", + "type": "github" + }, + { + "name": "ENA Token (Etherscan)", + "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "warning", + "notes": "ENA has rate-limited but discretionary minting controlled by the Dev Multisig. Maximum 10% of total supply per mint, with 365-day cooldown between mints. No tokenholder approval required. Total supply is 15 billion ENA.", + "evidence": [ + { + "name": "Mint Function", + "summary": "The `mint()` function allows the owner to create new tokens subject to two constraints:\n\n**MAX_INFLATION = 10** (10% of total supply per mint)\n\n**MINT_WAIT_PERIOD = 365 days** (minimum time between mints)\n\nOwner (Dev Multisig) can invoke without tokenholder approval.", + "urls": [ + { + "name": "ENA.sol `mint()` function (lines 42-49)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol#L42-L49", + "type": "github" + }, + { + "name": "ENA Token totalSupply", + "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#readContract", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "warning", + "notes": "Gatekeepers can disable USDe minting/redemption globally. Only the Owner can re-enable. The StakingRewardsDistributor operator (a single EOA) controls when rewards are distributed to sUSDe stakers.", + "evidence": [ + { + "name": "Privileged Roles (per Multisig Matrix)", + "summary": "**Owner (EthenaMinting):** Can set max mint/redeem limits for USDe, add/remove collateral assets and custodians.\n\n**Admin (EthenaMinting):** Can grant/revoke Minter, Redeemer, Gatekeeper roles.\n\n**Gatekeeper:** Can call `disableMintRedeem()` to halt USDe operations globally.\n\n**Operator (StakingRewardsDistributor):** Single EOA that controls `transferInRewards()` to distribute USDe to sUSDe stakers.", + "urls": [ + { + "name": "Multisig Matrix Documentation", + "url": "https://docs.ethena.fi/solution-design/key-trust-assumptions/matrix-of-multisig-and-timelocks", + "type": "docs" + }, + { + "name": "EthenaMinting V2", + "url": "https://etherscan.io/address/0xe3490297a08d6fC8Da46Edb7B6142E4F461b62D3#code", + "type": "explorer" + } + ] + }, + { + "name": "Gatekeeper Powers", + "summary": "Gatekeepers can halt but **only the Owner can re-enable**. This creates asymmetric power.", + "urls": [ + { + "name": "`disableMintRedeem()` (line 278)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/EthenaMinting.sol#L278", + "type": "github" + }, + { + "name": "`removeMinterRole()` (line 339)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/EthenaMinting.sol#L339", + "type": "github" + }, + { + "name": "`removeRedeemerRole()` (line 345)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/EthenaMinting.sol#L345", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "warning", + "notes": "The ENA base token has no blacklist capability. However, sENA and sUSDe staking contracts include BLACKLIST_MANAGER_ROLE with freeze and seizure powers. FULL_RESTRICTED addresses cannot transfer tokens, and `redistributeLockedAmount()` allows admin to seize frozen assets.", + "evidence": [ + { + "name": "sENA/sUSDe Blacklist Capabilities", + "summary": "StakedUSDe.sol defines three restriction roles:\n\n**SOFT_RESTRICTED_STAKER_ROLE:** Cannot stake/unstake\n\n**FULL_RESTRICTED_STAKER_ROLE:** Cannot transfer at all (frozen)\n\n**BLACKLIST_MANAGER_ROLE:** Can assign restrictions\n\nThe `redistributeLockedAmount()` function allows admin to seize and redistribute frozen assets.", + "urls": [ + { + "name": "StakedUSDe.sol blacklist roles (lines 26-32)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/StakedUSDe.sol#L26-L32", + "type": "github" + }, + { + "name": "`redistributeLockedAmount()` (lines 106-127)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/StakedUSDe.sol#L106-L127", + "type": "github" + }, + { + "name": "sUSDe Token", + "url": "https://etherscan.io/address/0x9d39a5de30e57443bff2a8307a4256c8797a3497#code", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "No active programmatic value accrual to ENA holders. **sUSDe holders receive USDe yield** from protocol operations; **sENA holders do NOT receive this yield**, only ecosystem airdrops. rsENA holders receive Symbiotic restaking rewards. Treasury flows through multisig-controlled wallets. All accrual mechanisms are controlled by multisigs or EOAs, not tokenholders.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "warning", + "notes": "**sUSDe holders receive USDe yield; sENA/ENA holders do NOT.** sENA holders receive only ecosystem airdrops from Ethena Network protocols. rsENA (~5.2M supply) earns Symbiotic restaking rewards via Mellow Finance. Fee switch (which would share protocol revenue with sENA) received positive forum signals but awaits Snapshot vote and execution.", + "evidence": [ + { + "name": "Fee Switch Status", + "summary": "Forum posts received positive signals in November 2024. USDe supply ~5.98B (the $6B threshold has **not** been met). Cumulative revenue $500M+ as of Sept 2025. Activation requires Risk Committee sign-off + Snapshot vote + multisig execution.", + "urls": [ + { + "name": "Fee Switch Parameters Proposal", + "url": "https://gov.ethenafoundation.com/t/ena-fee-switch-parameters/396", + "type": "docs" + }, + { + "name": "USDe Token (verify totalSupply)", + "url": "https://etherscan.io/address/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#readContract", + "type": "explorer" + }, + { + "name": "DefiLlama - Ethena Fees", + "url": "https://defillama.com/protocol/ethena", + "type": "docs" + } + ] + }, + { + "name": "Ethena Network Airdrops", + "summary": "Protocols in the Ethena Network commit portions of their token supply to sENA holders. Example: Ethereal committed 15% of tokens to sENA holders.", + "urls": [ + { + "name": "Ethena Network Documentation", + "url": "https://docs.ethena.fi/ethena-network", + "type": "docs" + } + ] + }, + { + "name": "rsENA (Restaked ENA via Symbiotic)", + "summary": "rsENA (~5.2M supply) provides economic security for USDe cross-chain transfers using LayerZero DVN messaging via Symbiotic partnership. rsENA holders receive rewards in **ENA and USDe** per docs. Available via **Mellow Finance vault**.", + "urls": [ + { + "name": "rsENA Contract", + "url": "https://etherscan.io/address/0xc65433845ecd16688eda196497fa9130d6c47bd8#readContract", + "type": "explorer" + }, + { + "name": "ENA Staking Documentation", + "url": "https://docs.ethena.fi/ena", + "type": "docs" + }, + { + "name": "Mellow Finance rsENA Vault", + "url": "https://app.mellow.finance/vaults/ethereum-rsena", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "warning", + "notes": "Protocol revenue flows through multisig-controlled wallets. **Treasury is NOT tokenholder-controlled.** Revenue flows: Hot Swap β†’ sUSDe Payout β†’ StakingRewardsDistributor β†’ sUSDe stakers. ENA tokenholders do NOT control treasury flows.", + "evidence": [ + { + "name": "Revenue Flow", + "summary": "Protocol Operations (delta-neutral strategies)\nβ†’ **Hot Swap Multisig** (receives revenue, converts to USDe)\nβ†’ **sUSDe Payout Multisig**\nβ†’ **StakingRewardsDistributor**\nβ†’ sUSDe Stakers\n\nENA/sENA holders are NOT in this flow unless fee switch activates.", + "urls": [ + { + "name": "Key Addresses Documentation", + "url": "https://docs.ethena.fi/solution-design/key-addresses", + "type": "docs" + }, + { + "name": "Hot Swap Multisig (revenue receiver)", + "url": "https://etherscan.io/address/0x4423198f26764a8ce9ac8f1683c476854c885d9d", + "type": "explorer" + }, + { + "name": "sUSDe Payout Multisig", + "url": "https://etherscan.io/address/0x71e4f98e8f20c88112489de3dded4489802a3a87", + "type": "explorer" + }, + { + "name": "StakingRewardsDistributor", + "url": "https://etherscan.io/address/0xf2fa332bd83149c66b09b45670bce64746c6b439#code", + "type": "explorer" + } + ] + }, + { + "name": "Reserve Fund (Negative Funding Backup)", + "summary": "Separate from revenue treasury. Used only for negative funding emergencies. NOT tokenholder-controlled.", + "urls": [ + { + "name": "Reserve Fund Multisig", + "url": "https://etherscan.io/address/0x2b5ab59163a6e93b4486f6055d33ca4a115dd4d5", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "warning", + "notes": "All ENA value accrual mechanisms are controlled by multisigs or EOAs, **NOT** by ENA tokenholders. Fee switch requires discretionary multisig execution. Ecosystem airdrops are Foundation-negotiated. sENA/rsENA can be upgraded without tokenholder vote.", + "evidence": [ + { + "name": "ENA Value Accrual Controls", + "summary": "**Fee Switch Activation:** Dev Multisig + Risk Committee. Snapshot votes are advisory only.\n\n**Ethena Network Airdrops:** Ethena Foundation negotiates allocations.\n\n**sENA Upgrade:** Dev Multisig via ProxyAdmin. Unilateral, no timelock.\n\n**rsENA Upgrade:** Separate multisig. Unilateral, no timelock.\n\n**rsENA Restaking Rewards:** Symbiotic integration.\n\n**Upgrade Risk:** Contract upgrades could modify or eliminate value accrual mechanisms without tokenholder approval.", + "urls": [ + { + "name": "Fee Switch Parameters Proposal", + "url": "https://gov.ethenafoundation.com/t/ena-fee-switch-parameters/396", + "type": "docs" + }, + { + "name": "sENA ProxyAdmin", + "url": "https://etherscan.io/address/0xf849d7792ff9b30a57656ee10a2776bcb49f4fe4", + "type": "explorer" + }, + { + "name": "rsENA ProxyAdmin Owner (5-of-8 Multisig)", + "url": "https://etherscan.io/address/0x27a907d1f809e8c03d806dc31c8e0c545a3187fc", + "type": "explorer" + } + ] + }, + { + "name": "Tokenholder Cannot Force Activation", + "summary": "Even with majority sENA/ENA support, tokenholders cannot force fee switch activation or change airdrop terms. Snapshot votes signal preference but the Dev Multisig decides execution. No onchain mechanism exists for binding governance over value accrual.", + "urls": [ + { + "name": "Governance Documentation", + "url": "https://docs.ethena.fi/solution-overview/governance", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "warning", + "notes": "USDe yield comes from three sources: CEX funding rates (unverifiable), ETH staking (~3-4%), and Treasury/BUIDL (~4-5%). **This yield flows to sUSDe stakers only; ENA/sENA holders receive none of it.** Revenue is controlled by multisigs, not programmatic.", + "evidence": [ + { + "name": "USDe Yield Sources", + "summary": "**CEX Funding Rates:** Delta-neutral hedging (long spot, short perps). Variable 5-20%+ yield. Not verifiable onchain (CEX positions).\n\n**ETH Staking:** stETH/wBETH collateral earns validator rewards (~3-4%). Partially verifiable (collateral visible).\n\n**Treasury/BUIDL:** USDtb backed by BlackRock BUIDL fund (~4-5%). Partially verifiable (USDtb holdings).", + "urls": [ + { + "name": "Coin Metrics Analysis (USDe Yield Sources)", + "url": "https://coinmetrics.substack.com/p/state-of-the-network-issue-335", + "type": "docs" + } + ] + }, + { + "name": "Yield Distribution Flow", + "summary": "1. Yield generated offchain (CEX funding) and onchain (staking, treasury)\n2. Revenue settles through Copper ClearLoop custody (offchain)\n3. Hot Swap multisig receives and converts to USDe\n4. sUSDe Payout multisig transfers to StakingRewardsDistributor\n5. Operator EOA calls `transferInRewards()` to distribute to **sUSDe stakers only**\n\n**ENA/sENA holders do NOT receive USDe yield.**", + "urls": [ + { + "name": "`transferInRewards()` (lines 88-94)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/StakingRewardsDistributor.sol#L88-L94", + "type": "github" + }, + { + "name": "DefiLlama - Ethena Fees", + "url": "https://defillama.com/protocol/ethena", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "All core contracts are verified on Etherscan and open source on GitHub. Multiple security audits completed.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The ENA token contract is verified on Etherscan and matches the source code on GitHub. Solidity version 0.8.20, GPL-3.0 license.", + "evidence": [ + { + "urls": [ + { + "name": "ENA Token (Etherscan)", + "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", + "type": "explorer" + }, + { + "name": "ENA.sol Source (GitHub)", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "All core protocol contracts are verified on Etherscan. Most have open source GitHub repos. **sENA is verified on Etherscan but no public GitHub repo has been identified.** Multiple audits completed.", + "evidence": [ + { + "name": "Verified Contracts", + "urls": [ + { + "name": "ENA Token", + "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", + "type": "explorer" + }, + { + "name": "sENA Proxy", + "url": "https://etherscan.io/address/0x8bE3460A480c80728a8C4D7a5D5303c85ba7B3b9#code", + "type": "explorer" + }, + { + "name": "rsENA Proxy", + "url": "https://etherscan.io/address/0xc65433845ecd16688eda196497fa9130d6c47bd8#code", + "type": "explorer" + }, + { + "name": "USDe Token", + "url": "https://etherscan.io/address/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code", + "type": "explorer" + }, + { + "name": "sUSDe Token", + "url": "https://etherscan.io/address/0x9d39a5de30e57443bff2a8307a4256c8797a3497#code", + "type": "explorer" + }, + { + "name": "EthenaMinting V2", + "url": "https://etherscan.io/address/0xe3490297a08d6fC8Da46Edb7B6142E4F461b62D3#code", + "type": "explorer" + }, + { + "name": "StakingRewardsDistributor", + "url": "https://etherscan.io/address/0xf2fa332bd83149c66b09b45670bce64746c6b439#code", + "type": "explorer" + } + ] + }, + { + "name": "GitHub Repository", + "urls": [ + { + "name": "Ethena Public Assets (GitHub)", + "url": "https://github.com/ethena-labs/bbp-public-assets", + "type": "github" + } + ] + }, + { + "name": "Audits", + "urls": [ + { + "name": "Code4rena 2023 Audit", + "url": "https://github.com/code-423n4/2023-10-ethena", + "type": "github" + }, + { + "name": "Code4rena 2024 Audit", + "url": "https://github.com/code-423n4/2024-11-ethena-labs", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "70% insider allocation. Vesting unlocks continue through April 2028 for all categories. Circulating supply is approximately 55% of total.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "warning", + "notes": "Initial allocation heavily favors insiders: Core Contributors (30%), Investors (25%), Foundation (15%), Ecosystem Development (28%), Binance Launchpool (2%). The combined insider bloc (Contributors + Investors + Foundation) controls **70%** of total supply.", + "evidence": [ + { + "name": "Token Allocation", + "summary": "Total supply: 15 billion ENA.\nCirculating: ~8.2 billion (55%).\nLocked: ~6.8 billion (45%).\n\n**Insider bloc:** Contributors (30%) + Investors (25%) + Foundation (15%) = 70%", + "urls": [ + { + "name": "Tokenomist.ai - ENA", + "url": "https://tokenomist.ai/ethena", + "type": "docs" + }, + { + "name": "ENA Tokenomics Documentation", + "url": "https://docs.ethena.fi/ena/tokenomics", + "type": "docs" + } + ] + } + ] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "warning", + "notes": "Monthly unlocks continue for all vesting categories through April 2028. Contributors, Investors, and Ecosystem receive linear monthly unlocks. Foundation allocation has no disclosed vesting.", + "evidence": [ + { + "name": "Vesting Schedules", + "summary": "**Core Contributors** (30%): 1-year cliff (25%), then 3-year linear monthly. Full unlock ~April 2028.\n\n**Investors** (25%): 1-year cliff (25%), then 3-year linear monthly. Full unlock ~April 2028.\n\n**Ecosystem Development** (28%): Linear over 4 years. Full unlock ~April 2028.\n\n**Foundation** (15%): Discretionary, no vesting disclosed.\n\nNext unlock: March 2, 2026 (~40.6M ENA to Contributors).", + "urls": [ + { + "name": "ENA Tokenomics Documentation", + "url": "https://docs.ethena.fi/ena/tokenomics", + "type": "docs" + }, + { + "name": "Tokenomist.ai - ENA Vesting", + "url": "https://tokenomist.ai/ethena", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Trademarks and IP owned by Ethena (BVI) Limited, not controlled by ENA tokenholders. Primary interfaces operate under BVI law. Code is open source but copyright belongs to Ethena Labs.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Trademarks and brand assets are owned by **Ethena (BVI) Limited** (Registration number 2127704), a British Virgin Islands entity. Per Terms of Service: \"The Company's name, trademarks and logos... are trademarks of the Company or its affiliates.\" This entity is NOT controlled by ENA tokenholders.", + "evidence": [ + { + "urls": [ + { + "name": "Ethena Terms of Service", + "url": "https://docs.ethena.fi/resources/terms-of-service", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "The primary interface domain (ethena.fi) and Terms of Service identify **Ethena (BVI) Limited** as the contracting party, governed by British Virgin Islands law. ENA holders have no legal claim or control over the primary interface.", + "evidence": [ + { + "urls": [ + { + "name": "Ethena Terms of Service", + "url": "https://docs.ethena.fi/resources/terms-of-service", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Smart contract code is licensed under GPL-3.0, making it open source. However, per Terms of Service: \"the Company and/or its licensors own all right, title and interest in and to the Services.\" Copyright belongs to Ethena Labs. ENA holders do NOT control IP or licensing rights.", + "evidence": [ + { + "urls": [ + { + "name": "Ethena GitHub License", + "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol", + "type": "github" + }, + { + "name": "Ethena Terms of Service", + "url": "https://docs.ethena.fi/resources/terms-of-service", + "type": "docs" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/ethfi.json b/src/data/generated/tokens/ethfi.json new file mode 100644 index 0000000..35971fb --- /dev/null +++ b/src/data/generated/tokens/ethfi.json @@ -0,0 +1,633 @@ +{ + "id": "ethfi", + "coingeckoId": "ether-fi", + "name": "ETHFI", + "symbol": "ETHFI", + "address": "0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB", + "icon": "https://assets.coingecko.com/coins/images/35958/standard/etherfi.jpeg", + "description": "EtherFi runs restaking infrastructure, liquid vaults, and savings products β€” expanding from LST into neobank territory. Governance is offchain with multisig execution. An active buyback program distributes purchased ETHFI to sETHFI holders; any funding from broader protocol revenue is currently discretionary.", + "network": "ethereum", + "lastUpdated": 1774549304, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://ether.fi/", + "twitter": "https://twitter.com/ether_fi", + "scan": "https://etherscan.io/token/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB" + }, + "infoDescription": "ether.fi is a liquid restaking protocol that enables users to stake ETH while maintaining liquidity through eETH tokens and participating in EigenLayer restaking.", + "positive": 3, + "neutral": 13, + "atRisk": 1, + "evidenceEntries": 20, + "score": { + "tokenId": "ethfi", + "passing": 3, + "total": 14, + "percentage": 21.428571428571427, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 1, + "total": 7, + "percentage": 14.285714285714285, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 1, + "total": 3, + "percentage": 33.33333333333333, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 1, + "total": 2, + "percentage": 50, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "at_risk", + "onchain-ctrl__role-accountability": "warning", + "onchain-ctrl__protocol-upgrade": "warning", + "onchain-ctrl__token-upgrade": "warning", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "warning", + "onchain-ctrl__censorship": "warning", + "val-accrual__active": "positive", + "val-accrual__treasury": "warning", + "val-accrual__mechanism": "warning", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "warning", + "verifiability__protocol-source": "positive", + "distribution__concentration": "warning", + "distribution__supply-schedule": "warning", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "ETHFI tokenholders do not have binding onchain control. Governance uses offchain voting with multisig execution.\n\nThe protocol uses a two-timelock system for upgrades and operations. A multisig controls the Upgrade Timelock, which owns the RoleRegistry and authorizes protocol upgrades.\n\nThe mainnet token is not upgradeable. L2 tokens on Arbitrum and Base are upgradeable by multisigs with no timelock.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "at_risk", + "notes": "No onchain Governor contract deployed. Governance uses offchain voting with a 4-day voting period and 1M ETHFI quorum. Execution is handled by multisig, meaning tokenholders can signal preference but cannot force execution.\n\nThe protocol has published a multi-stage decentralisation roadmap and is currently in Phase 0, which focuses on launching the token and establishing the initial voter base. Phase 1 targets full Governor deployment with treasury access.", + "evidence": [ + { + "name": "Governance Structure", + "summary": "Phase 0 focuses on launching the token and establishing the initial voter base. Phase 1 targets full Governor deployment with treasury access.", + "urls": [ + { + "name": "Governance Info", + "url": "https://vote.ether.fi/info", + "type": "docs" + }, + { + "name": "Governance Resources", + "url": "https://governance.ether.fi/t/ether-fi-governance-official-resources/2140", + "type": "docs" + }, + { + "name": "Governance Roadmap", + "url": "https://etherfi.gitbook.io/gov/governance-roadmap", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "warning", + "notes": "The protocol uses a two-timelock system. The Upgrade Timelock owns the RoleRegistry and controls protocol upgrades. The Operating Timelock handles day-to-day operations.\n\nTokenholders do not elect or control the multisig signers. Team-controlled multisigs propose all timelock operations.", + "evidence": [ + { + "name": "RoleRegistry Owner (Upgrade Timelock)", + "summary": "The RoleRegistry is owned by the Upgrade Timelock (72h delay). Role changes require timelock approval.", + "urls": [ + { + "name": "RoleRegistry Contract", + "url": "https://etherscan.io/address/0x62247D29B4B9BECf4BB73E0c722cf6445cfC7cE9", + "type": "explorer" + }, + { + "name": "Upgrade Timelock (72h)", + "url": "https://etherscan.io/address/0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761", + "type": "explorer" + } + ] + }, + { + "name": "Two-Timelock System", + "summary": "Upgrade Timelock (72h delay, 4-of-7 proposer) for upgrades. Operating Timelock (8h delay, 3-of-5 proposer) for routine operations.", + "urls": [ + { + "name": "Upgrade Admin (4-of-7)", + "url": "https://etherscan.io/address/0xcdd57d11476c22d265722f68390b036f3da48c21", + "type": "explorer" + }, + { + "name": "Operating Timelock (8h)", + "url": "https://etherscan.io/address/0xcD425f44758a08BaAB3C4908f3e3dE5776e45d7a", + "type": "explorer" + }, + { + "name": "Operating Admin (3-of-5)", + "url": "https://etherscan.io/address/0x2aCA71020De61bb532008049e1Bd41E451AE8AdC", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "warning", + "notes": "Core protocol contracts (LiquidityPool, eETH, weETH, EtherFiAdmin) are owned by the Upgrade Timelock, which enforces a delay before upgrades execute.\n\nBoring Vaults (sETHFI, eUSD, weETHs, weETHk) use a different pattern: they are non-upgradeable but controlled via RolesAuthority contracts owned by multisigs. L2 ETHFI tokens can be upgraded instantly by multisigs with no timelock.", + "evidence": [ + { + "name": "Upgrade Path", + "summary": "Core contracts are owned by the Upgrade Timelock (72h delay). Boring Vaults are non-upgradeable but controlled via RolesAuthority.", + "urls": [ + { + "name": "RoleRegistry Upgrade Check", + "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/RoleRegistry.sol#L76-L78", + "type": "github" + }, + { + "name": "LiquidityPool Upgrade Authorization", + "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/LiquidityPool.sol#L529-L531", + "type": "github" + } + ] + }, + { + "name": "RoleRegistry Owner", + "summary": "The RoleRegistry is owned by the Upgrade Timelock. Core contract upgrades require a 72-hour delay.", + "urls": [ + { + "name": "RoleRegistry Contract", + "url": "https://etherscan.io/address/0x62247D29B4B9BECf4BB73E0c722cf6445cfC7cE9", + "type": "explorer" + }, + { + "name": "Upgrade Timelock", + "url": "https://etherscan.io/address/0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "warning", + "notes": "The Ethereum mainnet ETHFI token is not upgradeable. L2 ETHFI tokens on Arbitrum and Base are upgradeable by multisigs with no timelock protection.\n\nL2 token holders face higher risk due to the ability to instantly upgrade the token contract.", + "evidence": [ + { + "name": "Mainnet Token (Not Upgradeable)", + "summary": "The Ethereum mainnet ETHFI token is not upgradeable. No EIP-1967 implementation slot exists.", + "urls": [ + { + "name": "ETHFI Token Contract", + "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", + "type": "explorer" + } + ] + }, + { + "name": "L2 Tokens (Upgradeable, No Timelock)", + "summary": "L2 ETHFI tokens are upgradeable proxies owned by 3-of-6 multisigs. No timelock protects L2 upgrades.", + "urls": [ + { + "name": "Arbitrum ETHFI", + "url": "https://arbiscan.io/address/0x7189fb5B6504bbfF6a852B13B7B82a3c118fDc27", + "type": "explorer" + }, + { + "name": "Arbitrum Admin (3-of-6)", + "url": "https://arbiscan.io/address/0x0c6ca434756eedf928a55ebeaf0019364b279732", + "type": "explorer" + }, + { + "name": "Base ETHFI", + "url": "https://basescan.org/address/0x6C240DDA6b5c336DF09A4D011139beAAa1eA2Aa2", + "type": "explorer" + }, + { + "name": "Base Admin (3-of-6)", + "url": "https://basescan.org/address/0x7a00657a45420044bc526b90ad667affaee0a868", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "ETHFI has a fixed supply of 1 billion tokens with no mint function. All tokens were minted at deployment. Supply can only decrease through the ERC20Burnable function. Approximately 1.46M tokens have been burned.", + "evidence": [ + { + "name": "Fixed Supply", + "summary": "No mint function exists in the contract. Holders can burn their own tokens via ERC20Burnable.", + "urls": [ + { + "name": "ETHFI Allocations", + "url": "https://etherfi.gitbook.io/gov/ethfi-allocations", + "type": "docs" + }, + { + "name": "Token Contract", + "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "warning", + "notes": "Protocol contracts can be paused by addresses holding the PROTOCOL_PAUSER role, which is assigned via the RoleRegistry (owned by Upgrade Timelock). The ETHFI token itself has no pause function.\n\neETH holders could be temporarily blocked from withdrawing to ETH if LiquidityPool is paused.", + "evidence": [ + { + "name": "Pause Authority", + "summary": "The EtherFiAdmin contract can pause protocol operations including the oracle, staking manager, auction manager, nodes manager, liquidity pool, and membership manager.", + "urls": [ + { + "name": "EtherFiAdmin Pause Function", + "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/EtherFiAdmin.sol#L102-L127", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "warning", + "notes": "The mainnet ETHFI token contains no blacklist, freeze, or transfer restriction mechanisms. L2 ETHFI tokens are upgradeable by multisigs with no timelock, which could allow introducing censorship functions through an upgrade.\n\nL1 token has no censorship risk. L2 tokens have potential censorship risk due to instant upgrade capability.", + "evidence": [ + { + "name": "Token Analysis", + "summary": "L1 token has no censorship capabilities. L2 tokens are upgradeable, creating a potential censorship vector on Arbitrum and Base.", + "urls": [ + { + "name": "ETHFI Token Contract", + "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", + "type": "explorer" + }, + { + "name": "Arbitrum ETHFI (Upgradeable)", + "url": "https://arbiscan.io/address/0x7189fb5B6504bbfF6a852B13B7B82a3c118fDc27", + "type": "explorer" + }, + { + "name": "Base ETHFI (Upgradeable)", + "url": "https://basescan.org/address/0x6C240DDA6b5c336DF09A4D011139beAAa1eA2Aa2", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "An active buyback program distributes purchased ETHFI to sETHFI holders. eETH withdrawal fees fund buybacks, while any additional funding from broader protocol revenue is currently Foundation-discretionary. The Treasury is a multisig controlled by the team.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "An ETHFI buyback program is operational, distributing purchased tokens to sETHFI holders. Buybacks are funded by eETH withdrawal fees weekly, and any additional contribution from broader protocol revenue is currently Foundation-discretionary.\n\nBuyback execution is controlled by a multisig where any single signer can execute. Distribution is announced via Foundation communications, not enforced by smart contract.", + "evidence": [ + { + "name": "Buyback Program", + "summary": "Buybacks documented in governance materials. The buyback wallet is a 1-of-5 multisig (any single signer can execute).", + "urls": [ + { + "name": "ETHFI Buyback Program", + "url": "https://etherfi.gitbook.io/gov/ethfi-buyback-program", + "type": "docs" + }, + { + "name": "Buyback History (Dune)", + "url": "https://dune.com/ether_fi/ethfi-buybacks-and-protocol-revenue-sources", + "type": "docs" + }, + { + "name": "sETHFI Staking", + "url": "https://www.ether.fi/app/ethfi", + "type": "docs" + }, + { + "name": "Buyback Wallet (1-of-5)", + "url": "https://etherscan.io/address/0x2f5301a3D59388c509C65f8698f521377D41Fd0F", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "warning", + "notes": "The primary Treasury is a multisig controlled by the team. The ETHFI Allocations documentation mentions multiple Safes under 'Treasury' β€” this analysis verified the main Treasury contract. Tokenholders have no direct control over treasury assets.", + "evidence": [ + { + "name": "Treasury Control", + "summary": "The verified Treasury is a 3-of-8 Gnosis Safe. Additional Treasury addresses may exist per ETHFI Allocations documentation.", + "urls": [ + { + "name": "Treasury Contract", + "url": "https://etherscan.io/address/0x0c83EAe1FE72c390A02E426572854931EefF93BA", + "type": "explorer" + }, + { + "name": "Upgrade Timelock", + "url": "https://etherscan.io/address/0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "warning", + "notes": "The percentage of protocol revenue allocated to buybacks is stated in documentation only β€” it is not hardcoded in any contract or set by an on-chain parameter. The Foundation has full discretion over actual buyback amounts and timing.\n\nFee recipients are set by admin roles via the RoleRegistry. Tokenholders cannot modify the fee structure through binding governance.", + "evidence": [ + { + "name": "Fee Configuration", + "summary": "Buyback percentages are stated in docs only, not enforced on-chain. Fee parameters are controlled by admin roles.", + "urls": [ + { + "name": "LiquidityPool Fee Functions", + "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/LiquidityPool.sol#L434-L439", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "Aragon developers have not verified additional offchain value accrual mechanisms. No legally binding revenue sharing arrangements or licensing revenue documented.", + "tags": [ + "Reference" + ], + "evidence": [] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "The ETHFI token contract is verified on Etherscan but not published to a public GitHub repository. All core protocol contracts are verified and open source under MIT license with multiple security audits and formal verification through Certora.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "warning", + "notes": "The ETHFI token contract is verified on Etherscan but is not published to a public GitHub repository. Protocol contracts are public, but the token contract is Etherscan-only.", + "evidence": [ + { + "name": "Token Verification", + "summary": "Token source verified on Etherscan. Compiler: Solidity 0.8.20, License: MIT.", + "urls": [ + { + "name": "Etherscan Verified Source", + "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "All core protocol contracts are verified on Etherscan and match the public GitHub repository. The code is MIT licensed with formal verification via Certora and multiple audits available.", + "evidence": [ + { + "name": "Protocol Source & Audits", + "urls": [ + { + "name": "etherfi-protocol/smart-contracts", + "url": "https://github.com/etherfi-protocol/smart-contracts", + "type": "github" + }, + { + "name": "Audit Reports", + "url": "https://github.com/etherfi-protocol/smart-contracts/tree/master/audits", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "Over 55% of tokens are allocated to Investors (33.74%) and Core Contributors (21.47%), subject to transparent vesting schedules published in official documentation. Vesting completion is expected by end of 2030.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "warning", + "notes": "Token allocation includes Investors at 33.74% (2-year vest, 1-year cliff), Treasury at 21.62%, Core Contributors at 21.47% (3-year vest, 1-year cliff), User Airdrops at 19.27%, and Partnerships at 3.9%. Vesting mitigates immediate concentration, and schedules are transparently documented.", + "evidence": [ + { + "name": "Token Allocation", + "urls": [ + { + "name": "ETHFI Allocations", + "url": "https://etherfi.gitbook.io/gov/ethfi-allocations", + "type": "docs" + } + ] + } + ] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "warning", + "notes": "Continuous unlocks from team and investor allocations occur according to published vesting schedules. Core Contributors have 3-year vesting with a 1-year cliff, while Investors have 2-year vesting with a 1-year cliff. Full vesting completion is expected by end of 2030.", + "evidence": [ + { + "name": "Vesting Schedule", + "urls": [ + { + "name": "ETHFI Allocations", + "url": "https://etherfi.gitbook.io/gov/ethfi-allocations", + "type": "docs" + }, + { + "name": "DefiLlama Unlocks", + "url": "https://defillama.com/unlocks/ether.fi", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Trademarks are owned by Ether.Fi SEZC (Cayman Islands company), not a tokenholder-controlled entity. The ether.fi domain and platform are operated by this company. Protocol smart contracts are MIT licensed, but non-contract IP has restricted licensing.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Trademarks are owned by Ether.Fi SEZC (Cayman Islands company), not a tokenholder-controlled entity. The Terms of Use state that company names, logos, and related designs are trademarks of the Company or its affiliates.", + "evidence": [ + { + "name": "Trademark Ownership", + "urls": [ + { + "name": "Terms of Use", + "url": "https://etherfi.gitbook.io/etherfi/ether.fi-legal/terms-of-use", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "The ether.fi domain and platform are operated by Ether.Fi SEZC, a Cayman Islands Special Economic Zone Company. There is no documented relationship between the company and DAO, and the company operates with unilateral control over terms and services.", + "evidence": [ + { + "name": "Legal Entity", + "urls": [ + { + "name": "Terms of Use", + "url": "https://etherfi.gitbook.io/etherfi/ether.fi-legal/terms-of-use", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Smart contracts are MIT licensed, allowing unrestricted use and modification. However, non-contract IP including website content, documentation, and brand assets is restricted. Users receive only a non-transferable, non-sublicensable, non-exclusive, revocable license for personal use.", + "evidence": [ + { + "name": "License", + "urls": [ + { + "name": "GitHub Repository", + "url": "https://github.com/etherfi-protocol/smart-contracts", + "type": "github" + }, + { + "name": "Terms of Use (Non-Contract IP)", + "url": "https://etherfi.gitbook.io/etherfi/ether.fi-legal/terms-of-use", + "type": "docs" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/ldo.json b/src/data/generated/tokens/ldo.json new file mode 100644 index 0000000..8f4e73f --- /dev/null +++ b/src/data/generated/tokens/ldo.json @@ -0,0 +1,706 @@ +{ + "id": "ldo", + "coingeckoId": "lido-dao", + "name": "LDO", + "symbol": "LDO", + "address": "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32", + "icon": "https://assets.coingecko.com/coins/images/13573/standard/Lido_DAO.png", + "description": "LDO is the native token of Lido, a liquid staking protocol with onchain voting, safeguarded by stETH holders via Dual Governance.", + "network": "ethereum", + "lastUpdated": 1777631939, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://lido.fi/", + "twitter": "https://twitter.com/lidofinance", + "scan": "https://etherscan.io/token/0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32" + }, + "infoDescription": "Lido is the leading liquid staking solution for Ethereum.", + "positive": 15, + "neutral": 0, + "atRisk": 0, + "evidenceEntries": 26, + "score": { + "tokenId": "ldo", + "passing": 12, + "total": 12, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 0, + "percentage": 0, + "evaluated": false, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "unevaluated", + "offchain__trademark": "positive", + "offchain__distribution": "positive", + "offchain__licensing": "positive" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "LDO holders exercise ultimate control through a multi-step governance flow with stETH holder veto protection via Dual Governance. All critical roles flow through governance-controlled contracts. The token has unbounded supply controlled by governance, with token behavior modifiable through controller upgrades.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "LDO holders exercise control through multiple governance paths with stETH holder veto protection via Dual Governance.", + "evidence": [ + { + "name": "Governance 1A", + "summary": "Voting (LDO holders) β†’ DualGovernance (stETH challenge window) β†’ EmergencyProtectedTimeLock (time delay) β†’ Executor β†’ Agent β†’ Protocol Contracts. LDO holders have ultimate control, constrained by stETH right to exit when disagreeing. This flow is used for protocol-related contracts.", + "urls": [ + { + "name": "Voting", + "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", + "type": "explorer" + }, + { + "name": "DualGovernance", + "url": "https://etherscan.io/address/0xC1db28B3301331277e307FDCfF8DE28242A4486E#code", + "type": "explorer" + }, + { + "name": "EmergencyProtectedTimeLock", + "url": "https://etherscan.io/address/0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316#code", + "type": "explorer" + }, + { + "name": "Executor", + "url": "https://etherscan.io/address/0x23E0B465633FF5178808F4A75186E2F2F9537021#code", + "type": "explorer" + }, + { + "name": "Agent", + "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + "type": "explorer" + } + ] + }, + { + "name": "Governance 1B", + "summary": "Voting (LDO holders) β†’ Protocol Contracts. This flow is used for DAO-related contracts.", + "urls": [ + { + "name": "Voting", + "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", + "type": "explorer" + } + ] + }, + { + "name": "Easy Track", + "summary": "An optimistic governance system where certain operations can be vetoed by LDO holders but are assumed to pass. Used for granting, treasury operations, and staking module management. Motions pass automatically unless β‰₯0.5% LDO objects within 72 hours. Permissionless execution post-timelock if unopposed; rejected motions escalate to full Aragon vote.", + "urls": [ + { + "name": "Easy Track Interface", + "url": "https://easytrack.lido.fi/", + "type": "docs" + }, + { + "name": "Easy Track Guide", + "url": "https://docs.lido.fi/guides/easy-track-guide", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "All critical roles flow through governance-controlled contracts, ensuring LDO holders maintain ultimate control over the protocol.", + "evidence": [ + { + "name": "Voting", + "summary": "CREATE_VOTES_ROLE allows proposing votes. All LDO holders can vote on proposals. The Voting contract controls the Agent via Governance 1A flow.", + "urls": [ + { + "name": "Voting", + "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", + "type": "explorer" + } + ] + }, + { + "name": "Agent", + "summary": "EXECUTE_ROLE is given to Executor. Executor is owned by EmergencyProtectedTimeLock, which is controlled by Governance 1A, which is controlled by Voting contract.", + "urls": [ + { + "name": "Agent", + "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + "type": "explorer" + } + ] + }, + { + "name": "TokenManager", + "summary": "MINT_ROLE is given to Voting contract. BURN_ROLE is given to no one, but its permission manager is Voting contract.", + "urls": [ + { + "name": "TokenManager (Aragon App V1 proxy)", + "url": "https://etherscan.io/address/0xf73a1260d222f447210581DDf212D915c09a3249", + "type": "explorer" + } + ] + }, + { + "name": "StakingRouter", + "summary": "STAKING_MODULE_MANAGE_ROLE is given to Agent contract. Since Agent is only callable by Governance 1, all operations are controlled by LDO holders.", + "urls": [ + { + "name": "StakingRouter", + "url": "https://etherscan.io/address/0xfddf38947afb03c621c71b06c9c70bce73f12999#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "Protocol contracts are upgradeable by LDO holders. Core governance contracts (DualGovernance, Executor, EmergencyProtectedTimeLock) are non-upgradeable.", + "evidence": [ + { + "name": "Upgradeable Contracts", + "summary": "StakingRouter: proxy_getAdmin is set to Agent.\n\nAgent: Uses Aragon v1 Proxy. Upgrading requires calling setApp(Agent, ...) on the Kernel, which requires APP_MANAGER_ROLE (currently given to Agent itself).", + "urls": [ + { + "name": "StakingRouter", + "url": "https://etherscan.io/address/0xfddf38947afb03c621c71b06c9c70bce73f12999#code", + "type": "explorer" + }, + { + "name": "Agent", + "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + "type": "explorer" + }, + { + "name": "Kernel", + "url": "https://etherscan.io/address/0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", + "type": "explorer" + } + ] + }, + { + "name": "Non-upgradeable Contracts", + "summary": "DualGovernance, Executor, and EmergencyProtectedTimeLock are immutable contracts that cannot be upgraded.", + "urls": [ + { + "name": "DualGovernance", + "url": "https://etherscan.io/address/0xC1db28B3301331277e307FDCfF8DE28242A4486E#code", + "type": "explorer" + }, + { + "name": "Executor", + "url": "https://etherscan.io/address/0x23E0B465633FF5178808F4A75186E2F2F9537021#code", + "type": "explorer" + }, + { + "name": "EmergencyProtectedTimeLock", + "url": "https://etherscan.io/address/0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "LDO token is immutable but **partially upgradeable** in practice due to its controller (TokenManager) being upgradeable via Aragon proxy.", + "evidence": [ + { + "name": "Token Contract", + "summary": "The LDO token contract is immutable with no proxy pattern. However, it has a controller address (TokenManager) that can call privileged functions (generateTokens, destroyTokens, enableTransfers, claimTokens). The token's doTransfer function includes a hook that calls the controller.", + "urls": [ + { + "name": "LDO Token", + "url": "https://etherscan.io/token/0x5a98fcbea516cf06857215779fd812ca3bef1b32#code", + "type": "explorer" + } + ] + }, + { + "name": "Controller Upgrade Path", + "summary": "TokenManager is upgradeable via Aragon proxy. Upgrading requires calling setApp(tokenManager, ...) on the Kernel, protected by APP_MANAGER_ROLE, which is assigned to the Agent contract (hence LDO holders).", + "urls": [ + { + "name": "TokenManager (Aragon App V1 proxy)", + "url": "https://etherscan.io/address/0xf73a1260d222f447210581DDf212D915c09a3249", + "type": "explorer" + }, + { + "name": "Kernel", + "url": "https://etherscan.io/address/0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "No supply cap exists, but all mints require LDO tokenholder approval through the Voting contract.", + "evidence": [ + { + "name": "Minting Path", + "summary": "The LDO token's controller (TokenManager) can mint unlimited tokens. However, minting requires MINT_ROLE on TokenManager, which is held by the Voting contract.\n\nMinting path: Voting (LDO holders) β†’ TokenManager β†’ Token.generateTokens()", + "urls": [ + { + "name": "MiniMeToken.generateTokens", + "url": "https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L385", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "positive", + "notes": "Deposits are operationally executed by Guardians (node operators + LDO dev team), but Guardians are fully accountable to LDO holders and cannot control protocol parameters.", + "evidence": [ + { + "name": "Guardian Role", + "summary": "User funds deposited into Lido are accumulated in the buffer and can only be deposited into a staking module by DepositSecurityModule, controlled by Guardians. Guardians use automated software for deposit operations.", + "urls": [ + { + "name": "DepositSecurityModule", + "url": "https://etherscan.io/address/0xfFA96D84dEF2EA035c7AB153D8B991128e3d72fD#code", + "type": "explorer" + }, + { + "name": "Lido.sol deposit logic", + "url": "https://github.com/lidofinance/core/blob/d5d92266b5bb305044c5dcf3e407463f776a4def/contracts/0.4.24/Lido.sol#L641", + "type": "github" + }, + { + "name": "Guardians use automated software", + "url": "https://docs.lido.fi/guides/deposit-security-manual/#tldr", + "type": "docs" + } + ] + }, + { + "name": "Guardian Accountability", + "summary": "Guardians are fully accountable to LDO holdersβ€”they can be rotated, replaced, or their mandate changed through onchain voting. Guardians do not control protocol parameters or economic risk (staking ratios, fee splits, module shares, or risk parameters).", + "urls": [ + { + "name": "Guardian Committee Membership", + "url": "https://docs.lido.fi/guides/deposit-security-manual#committee-membership", + "type": "docs" + }, + { + "name": "Guardians cannot control stake allocation", + "url": "https://github.com/lidofinance/core/blob/d5d92266b5bb305044c5dcf3e407463f776a4def/contracts/0.4.24/Lido.sol#L647", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "Controller has burn and transfer-disable capabilities, but BURN_ROLE is currently unassigned. Enabling burning requires LDO governance approval.", + "evidence": [ + { + "name": "Burn Capability", + "summary": "Controller can burn tokens from any address via destroyTokens. Currently, BURN_ROLE on TokenManager is given to no one, but permission manager is Voting contract. To enable burning, proposal must go through Governance 1B to call setPermission on ACL.", + "urls": [ + { + "name": "MiniMeToken.destroyTokens", + "url": "https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L401", + "type": "github" + }, + { + "name": "Aragon ACL", + "url": "https://etherscan.io/address/0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb", + "type": "explorer" + } + ] + }, + { + "name": "Transfer Control", + "summary": "Controller can enable/disable transfers globally via enableTransfers function.", + "urls": [ + { + "name": "MiniMeToken.enableTransfers", + "url": "https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L419", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "Protocol revenue flows to the LDO-controlled DAO treasury, and a newly approved manual buyback program (up to 10,000 stETH, held by the treasury) creates net buy pressure on LDO, though not a direct distribution. Treasury is controlled by LDO holders; offchain IP is held by DAO-controlled BORG foundations.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "**Protocol fee flow to treasury:**\n- Lido charges a 10% protocol fee on all ETH staking rewards, split onchain by the StakingRouter into a module fee (to node operators) and a treasury fee (to the LDO-controlled DAO Agent).\n- Calling `getStakingFeeAggregateDistribution()` on the StakingRouter currently returns an aggregated treasury fee of ~6.15% and module fees of ~3.85% (basePrecision = 100), meaning ~6.15% of all ETH staking rewards accrue to the DAO treasury on every oracle report.\n\n**Buyback program:**\n- Governance has authorized the Lido Growth Committee to buy back LDO using up to 10,000 stETH (from the accumulated fee described above), in 1,000 stETH batches via Easy Track motions (3-day objection window for LDO holders, 3% max slippage) while a favorable LDO/stETH ratio persists. Execution spans onchain (CoW, 1inch, Uniswap) and offchain venues (Binance, Bybit, OKX, Gate, Bitget).\n- This is independent of the anticipated automated NEST buybacks in Q2 2026. Value accrues to the LDO-controlled DAO treasury (buyback-and-hold), but is not directly distributed to LDO holders.", + "evidence": [ + { + "name": "Treasury Fee Flow", + "urls": [ + { + "name": "StakingRouter (read contract)", + "url": "https://etherscan.io/address/0xFdDf38947aFB03C621C71b06C9C70bce73f12999#readProxyContract", + "type": "explorer" + }, + { + "name": "StakingRouter.sol#L1051 (getStakingFeeAggregateDistribution)", + "url": "https://github.com/lidofinance/core/blob/master/contracts/0.8.9/StakingRouter.sol#L1051", + "type": "github" + } + ] + }, + { + "name": "Buyback Program", + "urls": [ + { + "name": "stETH/LDO Buyback Proposal", + "url": "https://research.lido.fi/t/utilizing-market-opportunities-steth-ldo-trade/11358", + "type": "docs" + }, + { + "name": "stETH/LDO Buyback Snapshot Vote", + "url": "https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x43be9ee8ce820d444f706e9dd763a223ebabf37be27931cc056888e6c2e48814", + "type": "vote" + }, + { + "name": "NEST", + "url": "https://research.lido.fi/t/nest-network-economic-support-tokenomics/10648", + "type": "docs" + }, + { + "name": "Liquid buybacks research", + "url": "https://research.lido.fi/t/liquid-buybacks-nest-execution-with-ldo-wsteth-liquidity/10894", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "positive", + "notes": "Treasury is the Agent contract, fully controlled by LDO holders. Treasury decisions are excluded from Governance 1 scope (stETH holders cannot challenge).", + "evidence": [ + { + "name": "Treasury Control", + "summary": "The Lido DAO Treasury is the Agent contract itself. The Treasury Management Committee proposes and enacts strategies via Governance 2 (Easy Track). All treasury decisions are controlled by LDO holders.", + "urls": [ + { + "name": "Agent (Treasury)", + "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + "type": "explorer" + } + ] + }, + { + "name": "Revenue Flow", + "summary": "LDO holders, via Governance 1A, control treasury revenue by approving staking modules and setting each module's treasury fee in the StakingRouter. This fee determines the portion of staking rewards routed to the Lido treasury.", + "urls": [ + { + "name": "StakingRouter", + "url": "https://etherscan.io/address/0xfddf38947afb03c621c71b06c9c70bce73f12999#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "To add a new staking module or update it with new fees, full governance flow required, hence controlled by LDO holders.", + "evidence": [ + { + "urls": [ + { + "name": "StakingRouter add/update", + "url": "https://github.com/lidofinance/core/blob/cca04b42123735714d8c60a73c2f7af949e989db/contracts/0.8.9/StakingRouter.sol#L227", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "Aragon has not verified additional offchain value accrual flows to the LDO token", + "evidence": [] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "LDO token source is publicly available and verified on Etherscan. Lido core protocol contracts are open source and verified.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The LDO token contract source code is publicly available on GitHub (MiniMeToken) and verified on Etherscan.", + "evidence": [ + { + "urls": [ + { + "name": "LDO Token (Etherscan)", + "url": "https://etherscan.io/token/0x5a98fcbea516cf06857215779fd812ca3bef1b32#code", + "type": "explorer" + }, + { + "name": "MiniMeToken Source (GitHub)", + "url": "https://github.com/aragon/minime", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "Lido core protocol contracts are open source on GitHub.", + "evidence": [ + { + "urls": [ + { + "name": "Lido Core Contracts (GitHub)", + "url": "https://github.com/lidofinance/core", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "Aragon has not yet verified the distribution of LDO holders outside of the core team.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "unevaluated", + "notes": "Aragon has not yet verified the distribution of LDO holders outside of the core team is greater than 50%", + "evidence": [] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "unevaluated", + "notes": "Aragon has not yet verified the supply schedule criteria for the LDO token", + "evidence": [] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "European trademark registrations for LIDO list Lido Labs Foundation as the owner. Lido Labs, Ecosystem, and Alliance BORG Foundations are DAO-controlled entities managing offchain IP and distribution.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "positive", + "notes": "European trademark registrations for LIDO list Lido Labs Foundation as the owner.", + "evidence": [ + { + "urls": [ + { + "name": "UK IPO Trademark Journal", + "url": "https://www.ipo.gov.uk/types/tm/t-os/t-tmj/tm-journals/2025-045/UK00004285645.html", + "type": "docs" + }, + { + "name": "Lido Logo", + "url": "https://euipo.europa.eu/eSearch/#details/trademarks/019182074", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "positive", + "notes": "Lido Labs BORG Foundation, Lido Ecosystem BORG Foundation, Lido Alliance BORG Foundation is a memberless DAO-adjacent foundation companies under which the Lido DAO has defined governance controls (including appointing/removing directors and overseeing BORG structures).", + "evidence": [ + { + "urls": [ + { + "name": "Lido Labs BORG Foundation", + "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xdf648307e68415e7b5cf96c6afbabd696c1731839f4b4a7cf5cb7efbc44ee9d6", + "type": "docs" + }, + { + "name": "Lido Ecosystem BORG Foundation", + "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0x7f72f12d72643c20cd0455c603d344050248e75ed1074c8391fae4c30f09ca15", + "type": "docs" + }, + { + "name": "Lido Alliance BORG Foundation", + "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xa478fa5518769096eda2b7403a1d4104ca47de3102e8a9abab8640ef1b50650c", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "positive", + "notes": "Lido Labs BORG Foundation, Lido Ecosystem BORG Foundation, Lido Alliance BORG Foundation is a memberless DAO-adjacent foundation companies under which the Lido DAO has defined governance controls (including appointing/removing directors and overseeing BORG structures).", + "evidence": [ + { + "urls": [ + { + "name": "Lido Labs BORG Foundation", + "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xdf648307e68415e7b5cf96c6afbabd696c1731839f4b4a7cf5cb7efbc44ee9d6", + "type": "docs" + }, + { + "name": "Lido Ecosystem BORG Foundation", + "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0x7f72f12d72643c20cd0455c603d344050248e75ed1074c8391fae4c30f09ca15", + "type": "docs" + }, + { + "name": "Lido Alliance BORG Foundation", + "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xa478fa5518769096eda2b7403a1d4104ca47de3102e8a9abab8640ef1b50650c", + "type": "docs" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/lqty.json b/src/data/generated/tokens/lqty.json new file mode 100644 index 0000000..8ee0b6a --- /dev/null +++ b/src/data/generated/tokens/lqty.json @@ -0,0 +1,542 @@ +{ + "id": "lqty", + "coingeckoId": "liquity", + "name": "LQTY", + "symbol": "LQTY", + "address": "0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D", + "icon": "https://assets.coingecko.com/coins/images/14665/standard/logo_V2.png", + "description": "LQTY is the secondary token of the Liquity protocol - a decentralised, immutable, and governance-free borrowing protocol that issues the LUSD (V1) and BOLD (V2) stablecoins.", + "network": "ethereum", + "lastUpdated": 1778064256, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://www.liquity.org/", + "twitter": "https://twitter.com/LiquityProtocol", + "scan": "https://etherscan.io/token/0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D" + }, + "infoDescription": "Liquity is a decentralised, immutable borrowing protocol that lets users mint stablecoins (LUSD in V1, BOLD in V2) against ETH and LST collateral with no governance over core parameters.", + "positive": 13, + "neutral": 1, + "atRisk": 2, + "evidenceEntries": 16, + "score": { + "tokenId": "lqty", + "passing": 13, + "total": 13, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "at_risk", + "offchain__distribution": "warning", + "offchain__licensing": "at_risk" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "LQTY holders' onchain power is voting on V2 Protocol Incentivized Liquidity (PIL) emissions. The rest of the protocol is described as \"Governance Free\" with immutable contracts and no admin keys, upgrade paths, or privileged roles.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "Neither LQTY holders nor the protocol team can influence core protocol execution - all core protocol parameters are immutable after launch. LQTY holders' onchain governance role is limited to voting on Protocol Incentivized Liquidity (PIL) emissions, directing a portion of V2 revenue to community-chosen liquidity initiatives.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity - Governance-Free", + "url": "https://www.liquity.org/features/governance-free", + "type": "docs" + }, + { + "name": "Directing Protocol Incentivized Liquidity with LQTY", + "url": "https://www.liquity.org/blog/directing-protocol-incentivized-liquidity-with-lqty", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "There are no privileged roles in the core protocol. All core protocol contracts are immutable after launch with no admin functions, owners, or upgrade keys.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity - Governance-Free", + "url": "https://www.liquity.org/features/governance-free", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "Core Liquity protocol contracts are non-upgradeable and do not use proxy patterns.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity v2 Technical Docs and Audits", + "url": "https://docs.liquity.org/v2-documentation/technical-docs-and-audits", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "The LQTY token contract is immutable with no proxy patterns or upgrade mechanisms.", + "evidence": [ + { + "urls": [ + { + "name": "LQTY Token Source", + "url": "https://etherscan.io/token/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "Fixed 100M LQTY token supply. No mint() function or inflation pathway in the bytecode.", + "evidence": [ + { + "urls": [ + { + "name": "The only LQTY token minting transactions ever, amounting to 100M LQTY tokens", + "url": "https://etherscan.io/advanced-filter?tkn=0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d&txntype=2&fadd=0x0000000000000000000000000000000000000000", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "positive", + "notes": "No privileged roles. The only thing LQTY holders can do is vote for PIL emissions.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity - Governance-Free", + "url": "https://www.liquity.org/features/governance-free", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "No Guardian or blacklist capabilities exist in the LQTY token contract.", + "evidence": [ + { + "urls": [ + { + "name": "LQTY token's implementation code", + "url": "https://etherscan.io/address/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d#code", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "Stakers earn two live onchain streams: V1 protocol fees (ETH redemption fees + LUSD borrowing fees) routed directly to LQTYStaking, and V2 bribes paid pro-rata to voters who allocate their voting power to initiatives. There is no protocol treasury - V2 sends 100% of revenue straight to users. Fee parameters and revenue routing are immutable and cannot be modified by governance or the team.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "LQTY holders receive two live, onchain value streams.\n\nLiquity V1 - protocol revenue to stakers. Staked LQTY earns fees routed directly to the V1 LQTYStaking contract: redemption fees (paid in ETH) are forwarded by TroveManager, and borrowing fees (paid in LUSD) are forwarded by BorrowerOperations. Every staker accrues a pro-rata share via the F_ETH and F_LUSD accumulators - no voting required.\n\nLiquity V2 - bribes to voters. V2 governance is built on top of V1 staking, so V2 participants automatically receive the V1 fee streams above. Stakers who additionally allocate their voting power to an initiative can claim a pro-rata share of bribes (BOLD plus an initiative-specific token) deposited by external parties for that epoch.", + "evidence": [ + { + "urls": [ + { + "name": "TroveManager.sol - V1 redemption fee β†’ increaseF_ETH", + "url": "https://github.com/liquity/dev/blob/3e64ee1b52c50d51587c64c1cf75e0ba82934979/packages/contracts/contracts/TroveManager.sol#L1011-L1012", + "type": "github" + }, + { + "name": "BorrowerOperations.sol - V1 borrowing fee β†’ increaseF_LUSD", + "url": "https://github.com/liquity/dev/blob/3e64ee1b52c50d51587c64c1cf75e0ba82934979/packages/contracts/contracts/BorrowerOperations.sol#L370", + "type": "github" + }, + { + "name": "Liquity docs - LQTY staking (V1 revenue + V2 bribes)", + "url": "https://docs.liquity.org/v2-faq/lqty-staking", + "type": "docs" + }, + { + "name": "V2-gov Governance.sol - depositLQTY (V2 deposit stakes into V1)", + "url": "https://github.com/liquity/V2-gov/blob/main/src/Governance.sol#L162", + "type": "github" + }, + { + "name": "V2-gov Governance.sol - allocateLQTY (vote on initiatives)", + "url": "https://github.com/liquity/V2-gov/blob/main/src/Governance.sol#L584", + "type": "github" + }, + { + "name": "V2-gov BribeInitiative.sol - depositBribe / claimBribes", + "url": "https://github.com/liquity/V2-gov/blob/main/src/BribeInitiative.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "positive", + "notes": "There is no protocol treasury. Liquity V2 skips the concept of a centralized treasury and sends 100% of its revenue straight to its users.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity V2 Docs", + "url": "https://www.liquity.org/blog/liquity-v2-is-live", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "The V1 fee accrual mechanism is immutable - neither the core team nor LQTY holders can change the fee parameters or the routing of fees to the LQTYStaking contract, since the core protocol contracts are non-upgradeable and expose no admin or governance hooks over these parameters. V2 governance (PIL + bribes) lets LQTY voters direct a separate slice of V2 revenue to liquidity initiatives, but explicitly has no control over core protocol parameters, which are immutable after launch.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity v2 Technical Docs and Audits", + "url": "https://docs.liquity.org/v2-documentation/technical-docs-and-audits", + "type": "docs" + }, + { + "name": "Directing Protocol Incentivized Liquidity with LQTY", + "url": "https://www.liquity.org/blog/directing-protocol-incentivized-liquidity-with-lqty", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "The protocol is entirely onchain - Aragon is not aware of any offchain entities towards which value accrues to the LQTY token or otherwise.", + "tags": [ + "Reference" + ], + "evidence": [] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "Both the LQTY token and core Liquity protocol contracts (V1 and V2) are open source on GitHub and source-verified against their onchain deployments - no closed-source components or unverified bytecode.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The LQTY token contract source code (LQTYToken.sol) is publicly available on GitHub and verified on Etherscan.", + "evidence": [ + { + "urls": [ + { + "name": "LQTY Token (Etherscan)", + "url": "https://etherscan.io/address/0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D#code", + "type": "explorer" + }, + { + "name": "LQTYToken.sol Source (GitHub)", + "url": "https://github.com/liquity/dev/blob/main/packages/contracts/contracts/LQTY/LQTYToken.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "Liquity protocol contracts are open source on GitHub.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity V2 Core (GitHub)", + "url": "https://github.com/liquity/bold", + "type": "github" + }, + { + "name": "Liquity V2 Governance (GitHub)", + "url": "https://github.com/liquity/V2-gov", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "LQTY supply is fully circulating - team and investor lockups ended in 2022, leaving only the immutable Stability Pool emission schedule for V1 depositors. Concentration among third parties has not yet been independently verified.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "unevaluated", + "notes": "Aragon has not yet verified that 3rd parties do not hold more than 50% of voting power", + "evidence": [] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "positive", + "notes": "It's all circulating - vesting ended in 2022, which can be verified by looking at the events emitted by the `LockupContractFactory`. The only remaining non-circulating LQTY is what the contract releases on an immutable schedule to Stability Pool depositors in V1.", + "evidence": [ + { + "urls": [ + { + "name": "LockupContractFactory (Etherscan)", + "url": "https://etherscan.io/address/0x2eBeF24dA09489218Ba2BECb01867F6DaAeDcD4B#code", + "type": "explorer" + }, + { + "name": "CommunityIssuance (Etherscan)", + "url": "https://etherscan.io/address/0xD8c9D9071123a059C6E0A945cF0e0c82b508d816#code", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Offchain dependencies for LQTY (trademark/brand, primary domain, and core software licensing) are controlled by Liquity AG, a Swiss company. LQTY tokenholders have no governance rights or control over Liquity AG. The core V1 protocol is immutable and governance-free, but brand, distribution, and V2 IP rights remain with the company.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "at_risk", + "notes": "The Liquity brand and related trademarks are owned and controlled by Liquity AG (Swiss company). No tokenholder-controlled legal entity is involved.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity AG - Crunchbase Company Profile", + "url": "https://www.crunchbase.com/organization/liquity-1d3e", + "type": "website" + }, + { + "name": "Liquity - Tracxn Company Profile", + "url": "https://tracxn.com/d/companies/liquity/__jQEYCp-QRDCuYvGmSSmUBbFIO6piP9rk6HEjxXEsrH0", + "type": "website" + }, + { + "name": "Team Page - Liquity.org", + "url": "https://www.liquity.org/team", + "type": "website" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Liquity AG controls the primary domain liquity.org and the frontend registry. However, the company explicitly does not run any user-facing frontend. All frontends are operated by independent third parties. No tokenholder-controlled entity controls distribution.", + "evidence": [ + { + "urls": [ + { + "name": "Liquity Runs on Decentralized Frontends - Official Blog", + "url": "https://www.liquity.org/blog/liquity-runs-on-decentralized-frontends", + "type": "website" + }, + { + "name": "Frontend Operators Page", + "url": "https://www.liquity.org/frontend-operators", + "type": "website" + }, + { + "name": "Liquity Launch Details (AG does not run frontend)", + "url": "https://www.liquity.org/blog/liquity-launch-details", + "type": "website" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "at_risk", + "notes": "Core protocol software and IP (V2) is owned by Liquity AG and released under a multi-year Business Source License (BUSL). Commercial deployments before ~September 2027 require approval from Liquity AG.", + "evidence": [ + { + "urls": [ + { + "name": "Licensing Liquity V2 - Official Blog", + "url": "https://www.liquity.org/blog/licensing-liquity-v2-may-the-fork-be-with-you", + "type": "website" + }, + { + "name": "Liquity V2 Core LICENSE File (GitHub)", + "url": "https://github.com/liquity/bold/blob/main/contracts/LICENSE", + "type": "github" + }, + { + "name": "What's New in Liquity V2 - Bankless", + "url": "https://www.bankless.com/read/whats-new-in-liquity-v2", + "type": "website" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/ondo.json b/src/data/generated/tokens/ondo.json new file mode 100644 index 0000000..5c29366 --- /dev/null +++ b/src/data/generated/tokens/ondo.json @@ -0,0 +1,868 @@ +{ + "id": "ondo", + "name": "ONDO", + "symbol": "ONDO", + "address": "0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3", + "icon": "https://assets.coingecko.com/coins/images/26580/standard/ONDO.png", + "description": "Ondo Finance tokenizes real-world assets: OUSG (US Treasuries), USDY (yield-bearing stablecoin), and Global Markets (tokenized equities). ONDO token has no control over these core products; team multisigs govern all ~$3.5B TVL.", + "network": "ethereum", + "lastUpdated": 1778674909, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://ondo.finance/", + "twitter": "https://twitter.com/OndoFinance", + "scan": "https://etherscan.io/token/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3" + }, + "infoDescription": "Ondo Finance is a tokenized real-world asset (RWA) protocol with ~$3.56B TVL across OUSG, USDY, and Global Markets. ONDO token governs Flux Finance (~$43M TVL), a Compound V2 fork for permissioned lending.", + "coingeckoId": "ondo-finance", + "positive": 4, + "neutral": 4, + "atRisk": 9, + "evidenceEntries": 27, + "score": { + "tokenId": "ondo", + "passing": 4, + "total": 15, + "percentage": 26.666666666666668, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 2, + "total": 7, + "percentage": 28.57142857142857, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 0, + "total": 4, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "at_risk", + "onchain-ctrl__role-accountability": "at_risk", + "onchain-ctrl__protocol-upgrade": "at_risk", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "at_risk", + "onchain-ctrl__access-gating": "at_risk", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "at_risk", + "val-accrual__treasury": "at_risk", + "val-accrual__mechanism": "at_risk", + "val-accrual__offchain": "warning", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "at_risk", + "distribution__supply-schedule": "warning", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "unevaluated" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "Core Ondo products are controlled by team multisigs. ONDO governance controls Flux Finance, but Flux appears small relative to Ondo's other product TVL. The ONDO token contract has team-held admin and minting roles. Supply is 10B with active mint capability.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "at_risk", + "notes": "ONDO tokenholders have no governance control over Ondo's core products (OUSG, USDY, Global Markets). These products represent ~$3.5B TVL (98.8% of total) and are controlled by company multisigs.", + "evidence": [ + { + "name": "OUSG/USDY/Global Markets Governance (Company-Controlled)", + "summary": "All core products are controlled by company multisigs with no tokenholder involvement:\n\n1. OUSG: Management Multisig holds DEFAULT_ADMIN_ROLE and ProxyAdmin ownership.\n\n2. USDY: Team Multisig holds ProxyAdmin ownership.\n\n3. Global Markets: TimelockController with 2-hour delay, controlled by company multisigs.\n\nNo forum discussion required. No onchain tokenholder vote. Changes proposed and executed by multisig signers.", + "urls": [ + { + "name": "OUSG", + "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#code", + "type": "explorer" + }, + { + "name": "USDY", + "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#code", + "type": "explorer" + }, + { + "name": "GMTokenManager", + "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c#code", + "type": "explorer" + }, + { + "name": "Management Multisig (OUSG admin)", + "url": "https://etherscan.io/address/0xAEd4caF2E535D964165B4392342F71bac77e8367", + "type": "explorer" + }, + { + "name": "USDY ProxyAdmin owner", + "url": "https://etherscan.io/address/0x1a694A09494E214a3Be3652e4B343B7B81A73ad7", + "type": "explorer" + }, + { + "name": "Global Markets TimelockController", + "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "at_risk", + "notes": "OUSG, USDY, and Global Markets are entirely controlled by company multisigs. The ONDO token itself has DEFAULT_ADMIN_ROLE and MINTER_ROLE held by team multisigs, not the DAO.", + "evidence": [ + { + "name": "Team-Controlled Roles (ONDO Token)", + "summary": "DEFAULT_ADMIN_ROLE: Team Multisig (4-of-7).\n\nMINTER_ROLE: Team Multisig (4-of-7).\n\nThe team can grant/revoke roles and mint new ONDO tokens without DAO approval.", + "urls": [ + { + "name": "Team Multisig", + "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", + "type": "explorer" + }, + { + "name": "ONDO Token", + "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", + "type": "explorer" + } + ] + }, + { + "name": "Team-Controlled Roles (OUSG/USDY)", + "summary": "OUSG DEFAULT_ADMIN_ROLE: Management Multisig (4-of-7).\n\nOUSG ProxyAdmin owner: Management Multisig (4-of-7).\n\nrOUSG ProxyAdmin owner: Management Multisig (4-of-7).\n\nUSDY ProxyAdmin owner: Team Multisig (4-of-7).\n\nrUSDY ProxyAdmin owner: Team Multisig (4-of-7).", + "urls": [ + { + "name": "OUSG", + "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92", + "type": "explorer" + }, + { + "name": "Management Multisig (4-of-7)", + "url": "https://etherscan.io/address/0xAEd4caF2E535D964165B4392342F71bac77e8367", + "type": "explorer" + }, + { + "name": "rOUSG", + "url": "https://etherscan.io/address/0x54043c656F0FAd0652D9Ae2603cDF347c5578d00", + "type": "explorer" + }, + { + "name": "USDY", + "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C", + "type": "explorer" + }, + { + "name": "Team Multisig (4-of-7)", + "url": "https://etherscan.io/address/0x1a694A09494E214a3Be3652e4B343B7B81A73ad7", + "type": "explorer" + }, + { + "name": "rUSDY", + "url": "https://etherscan.io/address/0xaf37c1167910ebC994e266949387d2c7C326b879", + "type": "explorer" + } + ] + }, + { + "name": "Team-Controlled Roles (Global Markets)", + "summary": "GMTokenManager/USDon DEFAULT_ADMIN_ROLE: TimelockController (2-hour delay).\n\nTimelockController PROPOSER_ROLE: Multisig (4-of-7).\n\nTimelockController EXECUTOR_ROLE: Multisig (1-of-8) + EOA.\n\nTimelockController DEFAULT_ADMIN_ROLE: Multisig (5-of-9).", + "urls": [ + { + "name": "GMTokenManager", + "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c#readContract", + "type": "explorer" + }, + { + "name": "Global Markets TimelockController", + "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", + "type": "explorer" + }, + { + "name": "PROPOSER_ROLE holder (Multisig 4-of-7)", + "url": "https://etherscan.io/address/0x71A4d411b5f7941Dee020417fca30413712f1646", + "type": "explorer" + }, + { + "name": "EXECUTOR_ROLE holder (Multisig 1-of-8)", + "url": "https://etherscan.io/address/0x2e55b738F5969Eea10fB67e326BEE5e2fA15A2CC", + "type": "explorer" + }, + { + "name": "EXECUTOR_ROLE holder (EOA)", + "url": "https://etherscan.io/address/0xfF1621Ee754512B34a6Bd62A941Cc4d5E4d0b85B", + "type": "explorer" + }, + { + "name": "DEFAULT_ADMIN_ROLE holder (Multisig 5-of-9)", + "url": "https://etherscan.io/address/0xcD35671dCAb88d05EE29dC4D360181529390B17f", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "at_risk", + "notes": "OUSG, USDY, and Global Markets are upgradeable contracts controlled by team multisigs on all chains. ONDO tokenholders have no upgrade control over any core products.", + "evidence": [ + { + "name": "OUSG/USDY (Team-Controlled)", + "summary": "Ethereum OUSG ProxyAdmin owner: Management Multisig (4-of-7).\n\nEthereum USDY ProxyAdmin owner: Team Multisig (4-of-7).\n\nPolygon OUSG ProxyAdmin owner: 3-of-6 Multisig.\n\nMantle USDY ProxyAdmin owner: 4-of-7 Multisig.\n\nArbitrum USDY ProxyAdmin owner: 4-of-7 Multisig.\n\nThe DAO has no upgrade authority over OUSG or USDY on any chain.", + "urls": [ + { + "name": "OUSG Proxy", + "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#readProxyContract", + "type": "explorer" + }, + { + "name": "OUSG ProxyAdmin Owner (Ethereum)", + "url": "https://etherscan.io/address/0xAEd4caF2E535D964165B4392342F71bac77e8367", + "type": "explorer" + }, + { + "name": "USDY Proxy", + "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#readProxyContract", + "type": "explorer" + }, + { + "name": "USDY ProxyAdmin Owner (Ethereum)", + "url": "https://etherscan.io/address/0x1a694A09494E214a3Be3652e4B343B7B81A73ad7", + "type": "explorer" + }, + { + "name": "Polygon OUSG ProxyAdmin Owner", + "url": "https://polygonscan.com/address/0x4413073440A568790c1b2b06B47F7D0a443574d0", + "type": "explorer" + }, + { + "name": "Mantle USDY ProxyAdmin Owner", + "url": "https://mantlescan.xyz/address/0xC8A7870fFe41054612F7f3433E173D8b5bFcA8E3", + "type": "explorer" + }, + { + "name": "Arbitrum USDY ProxyAdmin Owner", + "url": "https://arbiscan.io/address/0xC4ac5c2fA461901b4D91832d03A7018092eDCb4D", + "type": "explorer" + } + ] + }, + { + "name": "Global Markets (Team-Controlled via TimelockController)", + "summary": "USDon is an upgradeable proxy. Upgrade authority resides with TimelockController (2-hour delay) which is controlled by company multisigs. ONDO tokenholders have no upgrade control.", + "urls": [ + { + "name": "USDon", + "url": "https://etherscan.io/address/0xAcE8E719899F6E91831B18AE746C9A965c2119F1#code", + "type": "explorer" + }, + { + "name": "Global Markets TimelockController", + "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "The ONDO token is not upgradeable (no proxy pattern). It uses AccessControl with DEFAULT_ADMIN_ROLE and MINTER_ROLE held by a team multisig, not the DAO.", + "evidence": [ + { + "name": "ONDO Token Roles", + "summary": "DEFAULT_ADMIN_ROLE holder: Team Multisig.\nMINTER_ROLE holder: Team Multisig.\n\nThe team can grant/revoke roles and mint new tokens. The DAO has no control over these roles.", + "urls": [ + { + "name": "ONDO Token", + "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", + "type": "explorer" + }, + { + "name": "Team Multisig", + "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", + "type": "explorer" + } + ] + }, + { + "name": "Contract Source Note", + "summary": "The deployed ONDO token uses AccessControl. The public ondo-v1 repository shows a simpler Ownable-based contract, indicating the deployed contract differs from the public repo.", + "urls": [ + { + "name": "Public ondo-v1 repo (differs from deployed)", + "url": "https://github.com/ondoprotocol/ondo-v1/blob/main/contracts/tokens/Ondo.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "at_risk", + "notes": "ONDO has a total supply of 10B tokens. MINTER_ROLE exists and is held by a team multisig. The supply is not immutably fixed.", + "evidence": [ + { + "name": "Current Supply", + "summary": "Total supply: 10,000,000,000 ONDO (verified onchain).\nTeam Multisig balance: ~5.9B ONDO (~59% of supply).\n\nDocumentation states \"no scheduled or planned inflation\" but this is a policy statement, not code enforcement.", + "urls": [ + { + "name": "ONDO Token totalSupply", + "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", + "type": "explorer" + }, + { + "name": "Token Documentation", + "url": "https://docs.ondo.foundation/ondo-token", + "type": "docs" + } + ] + }, + { + "name": "Mint Function Exists", + "summary": "The deployed ONDO token includes a `mint()` function restricted to MINTER_ROLE holders. Team multisig holds MINTER_ROLE and can mint new tokens without DAO approval.", + "urls": [ + { + "name": "ONDO Token (verified source)", + "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#code", + "type": "explorer" + }, + { + "name": "Team Multisig (MINTER_ROLE holder)", + "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "at_risk", + "notes": "OUSG and USDY have extensive transfer restrictions (KYC requirements, blocklists, sanctions checks) enforced by Ondo Finance, not the DAO. The company controls who can hold these products.", + "evidence": [ + { + "name": "OUSG Transfer Restrictions", + "summary": "KYC required via KYCRegistry. The `_beforeTokenTransfer` hook enforces three-way checks: sender, receiver, and msg.sender must all be KYC-approved. The DAO does not control the KYC registry.", + "urls": [ + { + "name": "OUSG Contract (KYC enforcement)", + "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#code", + "type": "explorer" + }, + { + "name": "OUSG KYC check (source)", + "url": "https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousg.sol#L62-L89", + "type": "github" + }, + { + "name": "KYCRegistry", + "url": "https://etherscan.io/address/0x56A5D911052323D688C731d516530878557463e7", + "type": "explorer" + } + ] + }, + { + "name": "USDY Transfer Restrictions", + "summary": "USDY has blocklist, allowlist, and sanctions list checks. Transfers require passing all three checks. These are controlled by the company, not the DAO.", + "urls": [ + { + "name": "USDY restrictions (source)", + "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/usdy/USDY.sol#L84-L115", + "type": "github" + }, + { + "name": "USDY Contract", + "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "The ONDO token has no blocklist, freeze, or seizure functions. Transfers are permissionless. Transfers were enabled in January 2024.", + "evidence": [ + { + "name": "No Censorship Capability", + "summary": "`transferAllowed = true` (verified onchain, enabled January 2024).\nNo blacklist mapping.\nNo pause function for transfers.\nNo force transfer or seize functions.\n\nThe token contract has a `whenTransferAllowed` modifier but transfers are permanently enabled.", + "urls": [ + { + "name": "ONDO Token", + "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", + "type": "explorer" + }, + { + "name": "Source code (ondo-v1)", + "url": "https://github.com/ondoprotocol/ondo-v1/blob/main/contracts/tokens/Ondo.sol", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "No active value accrual mechanism was identified for ONDO holders. Ondo has multiple products with documented product-level economics, including yield, fees, expenses, and spreads, but no identified flow from those economics to ONDO holders or an ONDO-controlled treasury. Flux is ONDO-governed but small relative to Ondo's other product TVL, so it does not materially change the assessment.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "at_risk", + "notes": "No active value accrual mechanism for ONDO tokenholders was identified. Ondo products have documented product-level economics, but Aragon did not identify a fee distributor, staking reward, buyback program, DAO treasury, or other mechanism routing product economics to ONDO holders.", + "evidence": [ + { + "name": "Product Economics vs ONDO Holder Accrual", + "summary": "Ondo has products with documented product-level economics, including yield, fees, expenses, and spreads. Those mechanics do not identify an ONDO-holder accrual mechanism.", + "urls": [ + { + "name": "OUSG Fees", + "url": "https://docs.ondo.finance/qualified-access-products/ousg/fees-and-taxes", + "type": "docs" + }, + { + "name": "OUSG Yield", + "url": "https://docs.ondo.finance/qualified-access-products/ousg/yield", + "type": "docs" + }, + { + "name": "USDY Product Economics", + "url": "https://docs.ondo.finance/general-access-products/usdy/comparison-stablecoins", + "type": "docs" + }, + { + "name": "Global Markets Fees", + "url": "https://docs.ondo.finance/ondo-global-markets/fees-and-taxes", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "at_risk", + "notes": "Aragon has not been able to identify a DAO treasury address for ONDO governance. No FeeDistributor or Treasury contract exists.", + "evidence": [ + { + "name": "No Treasury Identified", + "summary": "No DAO treasury contract identified. No fee distribution mechanism was identified for Ondo product economics. No governance proposals for treasury creation were identified.", + "urls": [ + { + "name": "Ondo DAO Proposals", + "url": "https://www.tally.xyz/gov/ondo-dao", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "at_risk", + "notes": "ONDO holders have no identified control over revenue routing for Ondo's core products. Core product parameters are controlled by company-held roles and multisigs. Flux is ONDO-governed but represents a small share of Ondo TVL.", + "evidence": [ + { + "name": "OUSG/USDY Price Oracles (Company-Controlled)", + "summary": "The price oracles that determine OUSG/USDY NAV are controlled by SETTER_ROLE-restricted `setPrice()` functions. ONDO tokenholders cannot control these oracle price-update roles.", + "urls": [ + { + "name": "OUSG Oracle", + "url": "https://etherscan.io/address/0x0502c5ae08E7CD64fe1AEDA7D6e229413eCC6abe", + "type": "explorer" + }, + { + "name": "USDY Oracle", + "url": "https://etherscan.io/address/0xA0219AA5B31e65Bc920B5b6DFb8EdF0988121De0", + "type": "explorer" + }, + { + "name": "RWAOracleExternalComparisonCheck.sol (setPrice)", + "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/rwaOracles/RWAOracleExternalComparisonCheck.sol#L147", + "type": "github" + }, + { + "name": "RWAOracleRateCheck.sol (setPrice rate limit)", + "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/rwaOracles/RWAOracleRateCheck.sol#L81-L90", + "type": "github" + } + ] + }, + { + "name": "Global Markets (Company-Controlled via TimelockController)", + "summary": "GMTokenManager admin is TimelockController (2-hour delay). ONDO tokenholders have no control over Global Markets fee parameters.", + "urls": [ + { + "name": "GMTokenManager", + "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c", + "type": "explorer" + }, + { + "name": "Global Markets TimelockController", + "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "warning", + "notes": "Ondo product economics may accrue through product-holder yield, issuer/operator fees, spreads, expenses, or service revenue, but no documented offchain value-accrual flow to ONDO holders was identified.", + "tags": [ + "Reference" + ], + "evidence": [ + { + "name": "Ondo Product Economics", + "summary": "Ondo products have documented product-level economics, including yield, fees, expenses, and spreads. These mechanics describe product economics, not ONDO-holder accrual. No onchain mechanism guarantees that residual issuer/operator economics flow to ONDO holders.", + "urls": [ + { + "name": "OUSG Fees", + "url": "https://docs.ondo.finance/qualified-access-products/ousg/fees-and-taxes", + "type": "docs" + }, + { + "name": "OUSG Yield", + "url": "https://docs.ondo.finance/qualified-access-products/ousg/yield", + "type": "docs" + }, + { + "name": "USDY Product Economics", + "url": "https://docs.ondo.finance/general-access-products/usdy/comparison-stablecoins", + "type": "docs" + }, + { + "name": "USDY Issuer / Ondo Finance Relationship", + "url": "https://docs.ondo.finance/general-access-products/usdy/important-notes", + "type": "docs" + }, + { + "name": "Global Markets Fees", + "url": "https://docs.ondo.finance/ondo-global-markets/fees-and-taxes", + "type": "docs" + } + ] + }, + { + "name": "Global Markets Revenue", + "summary": "Global Markets documentation describes fees and quote spreads retained by Ondo Global Markets. Aragon did not identify a mechanism routing those fees or spreads to ONDO holders or an ONDO-controlled treasury.", + "urls": [ + { + "name": "Global Markets Fees", + "url": "https://docs.ondo.finance/ondo-global-markets/fees-and-taxes", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "ONDO token is verified on Etherscan. OUSG/USDY/Global Markets contracts are verified with public GitHub and audit code available. The deployed ONDO token uses AccessControl, which differs from the public ondo-v1 repository.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The ONDO token contract is verified on Etherscan. The deployed contract uses AccessControl, which differs from the simpler Ownable-based contract in the public ondo-v1 repository.", + "evidence": [ + { + "urls": [ + { + "name": "ONDO Token (Etherscan verified)", + "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#code", + "type": "explorer" + }, + { + "name": "Public ondo-v1 repo (differs from deployed)", + "url": "https://github.com/ondoprotocol/ondo-v1/blob/main/contracts/tokens/Ondo.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "OUSG, USDY, and Global Markets contracts are verified on Etherscan. Public GitHub repositories and audit code available.", + "evidence": [ + { + "name": "OUSG/USDY", + "urls": [ + { + "name": "OUSG", + "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#code", + "type": "explorer" + }, + { + "name": "USDY", + "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#code", + "type": "explorer" + }, + { + "name": "USDY GitHub", + "url": "https://github.com/ondoprotocol/usdy", + "type": "github" + }, + { + "name": "Audit code (Code4rena)", + "url": "https://github.com/code-423n4/2024-03-ondo-finance", + "type": "github" + } + ] + }, + { + "name": "Global Markets", + "urls": [ + { + "name": "GMTokenManager", + "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c#code", + "type": "explorer" + }, + { + "name": "USDon", + "url": "https://etherscan.io/address/0xAcE8E719899F6E91831B18AE746C9A965c2119F1#code", + "type": "explorer" + }, + { + "name": "USDonManager", + "url": "https://etherscan.io/address/0x05CCbB4b74854f8A067b83475E8c34f5a413D7e1#code", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "~59% of ONDO supply is held by a single team multisig, giving effective governance control. Vesting schedules exist per documentation but specific addresses are not publicly verifiable onchain.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "at_risk", + "notes": "~59% of ONDO supply is held by a single team multisig. This gives the team unilateral control over governance. The team can pass any proposal and block any proposal.", + "evidence": [ + { + "name": "Team Holdings", + "summary": "Team Multisig balance: ~5.9B ONDO (~59% of supply).\nTotal supply: 10B ONDO.\nMultisig config: 4-of-7.\n\nThis multisig also holds DEFAULT_ADMIN_ROLE and MINTER_ROLE on the ONDO token. \"Decentralized governance\" is effectively team governance.", + "urls": [ + { + "name": "Team Multisig holdings", + "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", + "type": "explorer" + }, + { + "name": "ONDO Token totalSupply", + "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "warning", + "notes": "Per documentation, unlock schedules exist for team and investors with unclear start dates, but the token being transferrable from Jan 2024 allows an educated guess of many unlocks yet to pan out. Aragon has not been able to verify specific vesting contract addresses from onchain data.", + "evidence": [ + { + "name": "Documented Schedule", + "summary": "CoinList Tranche 1 (~0.3%): 1-year lock, then 18-month linear release. \n\nCoinList Tranche 2 (~1.7%): 1-year lock, then 6-month linear release. \n\nSeed Investors (<7%): 1-year cliff, then 48-month release. \n\nSeries A (<7%): 1-year cliff, then 48-month release. \n\nCore Team: 5-year extended lock-up from the transfer unlock.\n\nAragon has not been able to verify specific vesting contract addresses or unlock schedules from onchain data.", + "urls": [ + { + "name": "Token Documentation", + "url": "https://docs.ondo.foundation/ondo-token", + "type": "docs" + }, + { + "name": "CoinList - ONDO Community Sale", + "url": "https://coinlist.co/ondo", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Ondo Finance Inc. operates the primary website, documentation, APIs, dashboards, and technical interfaces under its Terms of Service. The Terms preserve Ondo-related trademark/IP rights and separate interface services from product issuance or economic terms. Certain product source files use BUSL-1.1 SPDX headers, but no tokenholder-controlled licensing right was identified.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Ondo's Terms of Service state that Ondo names, logos, and marks used on the site or services are owned by Ondo, its affiliates, Covered Entities, or applicable licensors.", + "evidence": [ + { + "name": "Ondo Terms of Service - Proprietary Rights", + "summary": "The Terms reserve Ondo names, logos, and marks to Ondo, its affiliates, Covered Entities, or applicable licensors, and do not grant users rights in those trademarks.", + "urls": [ + { + "name": "Terms of Service", + "url": "https://docs.ondo.finance/legal/terms-of-service", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Ondo Finance Inc. controls the primary website, documentation, APIs, dashboards, and technical interfaces. The Terms of Service identify Ondo Finance Inc. as the contracting party for those interface services, while product issuance and economic terms are governed separately by Covered Entity terms.", + "evidence": [ + { + "name": "Ondo Terms of Service - Interface Services", + "summary": "Ondo Finance Inc. operates the site and interface services. The Terms also state that Covered Entities are separate legal entities providing their own services under their own terms.", + "urls": [ + { + "name": "Terms of Service", + "url": "https://docs.ondo.finance/legal/terms-of-service", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "unevaluated", + "notes": "Certain USDY/rOUSG source files use SPDX-License-Identifier: BUSL-1.1. BUSL-1.1 is not an open-source license and restricts production/commercial use until the applicable change date or open-source conversion.", + "evidence": [ + { + "name": "BUSL-1.1 Source Headers", + "summary": "USDY and rOUSG source files include BUSL-1.1 SPDX headers. No repo-level license file or tokenholder-controlled license holder was identified.", + "urls": [ + { + "name": "USDY source SPDX header", + "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/usdy/USDY.sol#L1", + "type": "github" + }, + { + "name": "rOUSG source SPDX header", + "url": "https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L1", + "type": "github" + }, + { + "name": "BUSL-1.1 license text", + "url": "https://spdx.org/licenses/BUSL-1.1.html", + "type": "docs" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/sky.json b/src/data/generated/tokens/sky.json new file mode 100644 index 0000000..2177339 --- /dev/null +++ b/src/data/generated/tokens/sky.json @@ -0,0 +1,673 @@ +{ + "id": "sky", + "coingeckoId": "sky", + "name": "SKY", + "symbol": "SKY", + "address": "0x56072C95FAA701256059aa122697B133aDEd9279", + "icon": "https://assets.coingecko.com/coins/images/39925/standard/sky.jpg", + "description": "SKY demonstrates strong ownership characteristics with binding onchain governance and active value accrual through buybacks. The token is non-upgradeable with no censorship capabilities. Trademarks remain with an independent foundation.", + "network": "ethereum", + "lastUpdated": 1774549304, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://sky.money/", + "twitter": "https://twitter.com/SkyEcosystem", + "scan": "https://etherscan.io/token/0x56072C95FAA701256059aa122697B133aDEd9279" + }, + "infoDescription": "Sky Protocol (formerly MakerDAO) issues two stablecoins: DAI (non-upgradeable) and USDS (upgradeable).", + "positive": 13, + "neutral": 1, + "atRisk": 0, + "evidenceEntries": 25, + "score": { + "tokenId": "sky", + "passing": 12, + "total": 12, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 0, + "percentage": 0, + "evaluated": false, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 1, + "total": 2, + "percentage": 50, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "unevaluated", + "offchain__trademark": "warning", + "offchain__distribution": "unevaluated", + "offchain__licensing": "positive" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "SKY holders exercise binding governance through an approval-based voting workflow with a 24-hour timelock before any changes take effect. The token is non-upgradeable with no censorship capabilities. All privileged roles trace back to governance.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "SKY holders vote to elect \"spells\" (upgrade contracts). The winning spell must wait 24 hours before it can execute changes. This creates binding, verifiable onchain governance.", + "evidence": [ + { + "name": "Governance Workflow", + "summary": "How votes become protocol changes:\n\n1. Lock SKY tokens in voting contract\n2. Vote for a \"spell\" (upgrade contract)\n3. Winning spell schedules execution via plot()\n4. 24-hour delay enforced (eta >= now + 86400s)\n5. After delay, anyone can trigger exec()\n\nThe plot() function enforces the delay β€” no spell can execute without waiting.", + "urls": [ + { + "name": "Voting Contract", + "url": "https://etherscan.io/address/0x929d9A1435662357F54AdcF64DcEE4d6b867a6f9#code", + "type": "explorer" + }, + { + "name": "plot() delay enforcement (L111-116)", + "url": "https://github.com/sky-ecosystem/ds-pause/blob/master/src/pause.sol#L111-L116", + "type": "github" + } + ] + }, + { + "name": "Timelock Contract", + "summary": "All governance actions execute through a timelock (Pause Proxy). The 24-hour delay gives users time to exit before changes take effect.", + "urls": [ + { + "name": "Pause Proxy (Etherscan)", + "url": "https://etherscan.io/address/0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB#code", + "type": "explorer" + }, + { + "name": "exec() function (L124-136)", + "url": "https://github.com/sky-ecosystem/ds-pause/blob/master/src/pause.sol#L124-L136", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "All privileged roles trace back to SKY governance. A single \"Pause Proxy\" contract holds admin rights on all core contracts. Only governance-approved spells can act through it.", + "evidence": [ + { + "name": "Core Contracts Controlled by Governance", + "summary": "The Pause Proxy holds admin rights on all key contracts. Verify by calling wards(0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB) on each contract β€” returns 1 if authorized.", + "urls": [ + { + "name": "Vat wards() β€” verify Pause Proxy", + "url": "https://etherscan.io/address/0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B#readContract", + "type": "explorer" + }, + { + "name": "Vow wards() β€” verify Pause Proxy", + "url": "https://etherscan.io/address/0xA950524441892A31ebddF91d3cEEFa04Bf454466#readContract", + "type": "explorer" + }, + { + "name": "SKY wards() β€” verify Pause Proxy", + "url": "https://etherscan.io/address/0x56072C95FAA701256059aa122697B133aDEd9279#readContract", + "type": "explorer" + } + ] + }, + { + "name": "Emergency Controls", + "summary": "\"Mom\" contracts can adjust parameters without the 24hr delay for emergencies. These are governance-controlled via the authority pattern.", + "urls": [ + { + "name": "Mom authority pattern (source)", + "url": "https://github.com/sky-ecosystem/osm-mom/blob/master/src/OsmMom.sol#L55", + "type": "github" + }, + { + "name": "OSM_MOM β€” verify authority()", + "url": "https://etherscan.io/address/0x76416A4d5190d071bfed309861527431304aA14f#readContract", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "Core accounting contracts are immutable β€” their code cannot change. Newer stablecoin contracts (USDS, sUSDS) can be upgraded, but only through governance.", + "evidence": [ + { + "name": "Immutable Core", + "summary": "Core accounting contracts have no upgrade mechanism. The deployed code is permanent.", + "urls": [ + { + "name": "Vat source code", + "url": "https://github.com/sky-ecosystem/dss/blob/master/src/vat.sol", + "type": "github" + }, + { + "name": "SKY token source code", + "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol", + "type": "github" + } + ] + }, + { + "name": "Upgradeable Stablecoins", + "summary": "USDS and sUSDS use proxy contracts. Upgrades require governance approval through the standard voting process.", + "urls": [ + { + "name": "USDS upgrade authorization", + "url": "https://github.com/sky-ecosystem/usds/blob/master/src/Usds.sol#L73", + "type": "github" + }, + { + "name": "sUSDS source code", + "url": "https://github.com/sky-ecosystem/stusds/blob/master/src/StUsds.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "The SKY token cannot be upgraded. Its code is permanent with no admin backdoors.", + "evidence": [ + { + "name": "Non-Upgradeable Token", + "summary": "Standard ERC-20 contract with no proxy pattern. The deployed code cannot be changed.", + "urls": [ + { + "name": "SKY Token Contract", + "url": "https://etherscan.io/address/0x56072C95FAA701256059aa122697B133aDEd9279#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "Only governance can mint new SKY. The primary source is conversion from legacy MKR tokens at a fixed 24,000:1 ratio. Anyone can burn their own tokens.", + "evidence": [ + { + "name": "Minting Restricted to Governance", + "summary": "The mint() function requires authorization. Only governance-controlled contracts can call it.", + "urls": [ + { + "name": "mint() function (L146-154)", + "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol#L146-L154", + "type": "github" + } + ] + }, + { + "name": "MKR Conversion", + "summary": "Legacy MKR tokens convert to SKY at a fixed 24,000:1 ratio. The rate is immutable in the contract.", + "urls": [ + { + "name": "rate() returns 24000", + "url": "https://etherscan.io/address/0xA1Ea1bA18E88C381C724a75F23a130420C403f9a#readContract", + "type": "explorer" + }, + { + "name": "Immutable rate declaration", + "url": "https://github.com/sky-ecosystem/sky/blob/master/src/MkrSky.sol#L36", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "positive", + "notes": "No admin can arbitrarily pause the protocol or restrict user access. Emergency shutdown exists but requires burning a significant amount of tokens.", + "evidence": [ + { + "name": "Emergency Shutdown", + "summary": "The only way to \"pause\" the protocol is Emergency Shutdown, which requires burning tokens past a threshold. This is a nuclear option, not an admin switch.", + "urls": [ + { + "name": "ESM source code", + "url": "https://github.com/sky-ecosystem/esm/blob/master/src/ESM.sol", + "type": "github" + }, + { + "name": "ESM contract on Etherscan", + "url": "https://etherscan.io/address/0x09e05fF6142F2f9de8B6B65855A1d56B6cfE4c58#code", + "type": "explorer" + } + ] + }, + { + "name": "No Arbitrary Pause", + "summary": "Core contracts have no pause function. Users can always interact with the protocol.", + "urls": [] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "SKY has no blacklist, freeze, or admin-controlled transfer blocking. Tokens cannot be seized or frozen by anyone.", + "evidence": [ + { + "name": "No Censorship Functions", + "summary": "The contract has no blacklist, freeze, or pause functions. Transfer functions are standard ERC-20 with no admin intervention points.", + "urls": [ + { + "name": "Transfer Functions (L96-135)", + "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol#L96-L135", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "Protocol revenue flows to SKY holders through automated buybacks. When the protocol earns fees, it uses them to buy SKY on the open market. All fee parameters are governance-controlled.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "A portion of protocol revenue (interest on loans, liquidation penalties) automatically flows to buybacks. The \"Smart Burn Engine\" purchases SKY using accumulated fees.", + "evidence": [ + { + "name": "Smart Burn Engine", + "summary": "Automated buyback system. When protocol surplus exceeds the buffer threshold, the Splitter routes fees to the Flapper, which purchases SKY on UniswapV2.", + "urls": [ + { + "name": "Splitter contract", + "url": "https://etherscan.io/address/0xBF7111F13386d23cb2Fba5A538107A73f6872bCF#code", + "type": "explorer" + }, + { + "name": "Flapper (buyback executor)", + "url": "https://etherscan.io/address/0x374D9c3d5134052Bc558F432Afa1df6575f07407#code", + "type": "explorer" + }, + { + "name": "Flapper source code", + "url": "https://github.com/sky-ecosystem/dss-flappers/blob/master/src/FlapperUniV2.sol", + "type": "github" + }, + { + "name": "Smart Burn Engine Dashboard", + "url": "https://info.sky.money/smart-burn-engine", + "type": "docs" + } + ] + }, + { + "name": "Revenue Flow", + "summary": "Protocol revenue flows: Stability Fees β†’ Vow (surplus) β†’ Splitter β†’ Flapper β†’ SKY buybacks.\n\nRevenue sources:\n- Interest on loans (stability fees)\n- Liquidation penalties\n- Stablecoin swap fees (PSM)", + "urls": [ + { + "name": "Stability fee accrual", + "url": "https://github.com/sky-ecosystem/dss/blob/master/src/jug.sol#L122-L128", + "type": "github" + }, + { + "name": "Surplus routing to flapper", + "url": "https://github.com/sky-ecosystem/dss/blob/master/src/vow.sol#L148-L152", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "positive", + "notes": "All treasury assets are held onchain and controlled by governance. There is no offchain treasury or multi-sig.", + "evidence": [ + { + "name": "Onchain Treasury", + "summary": "Protocol surplus is held in the Vow contract. Only governance can access these funds.", + "urls": [ + { + "name": "Treasury Contract", + "url": "https://etherscan.io/address/0xA950524441892A31ebddF91d3cEEFa04Bf454466#readContract", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "Governance controls all fee rates and revenue parameters. SKY holders decide how much the protocol charges and how revenue is distributed.", + "evidence": [ + { + "name": "Fee Rate Control", + "summary": "Governance sets stability fee rates via the Jug contract. Each collateral type has its own rate, updated through governance spells.", + "urls": [ + { + "name": "Jug fee configuration", + "url": "https://github.com/sky-ecosystem/dss/blob/master/src/jug.sol#L107-L111", + "type": "github" + }, + { + "name": "Jug contract", + "url": "https://etherscan.io/address/0x19c0976f590D67707E62397C87829d896Dc0f1F1#code", + "type": "explorer" + } + ] + }, + { + "name": "Surplus Buffer Control", + "summary": "The surplus buffer (\"hump\") determines when buybacks trigger. Governance can adjust this threshold to control the pace of buybacks.", + "urls": [ + { + "name": "Vow hump configuration", + "url": "https://github.com/sky-ecosystem/dss/blob/master/src/vow.sol#L96-L103", + "type": "github" + }, + { + "name": "Vow contract β€” read hump", + "url": "https://etherscan.io/address/0xA950524441892A31ebddF91d3cEEFa04Bf454466#readContract", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "No verified offchain revenue streams flow to SKY holders. The DAI Foundation is an independent non-profit that does not distribute profits.", + "tags": [ + "Reference" + ], + "evidence": [ + { + "name": "Foundation Independence", + "summary": "The DAI Foundation operates independently under Danish law. It holds trademarks but does not distribute value to tokenholders.", + "urls": [ + { + "name": "Foundation Mandate", + "url": "https://daifoundation.org/mandate", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "All code is open source (AGPL-3.0) and verified on Etherscan. Anyone can audit the contracts.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "SKY token source code is open source and verified on Etherscan. Anyone can audit the code.", + "evidence": [ + { + "name": "Verified Source", + "summary": "Source code matches deployed bytecode. Licensed under AGPL-3.0.", + "urls": [ + { + "name": "Source Code", + "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol", + "type": "github" + }, + { + "name": "Verified on Etherscan", + "url": "https://etherscan.io/address/0x56072C95FAA701256059aa122697B133aDEd9279#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "Every protocol contract is open source. A \"chainlog\" provides a registry of all deployed contract addresses.", + "evidence": [ + { + "name": "Open Source Repositories", + "summary": "All protocol code is open source under AGPL-3.0. Core contracts, governance, and stablecoins all have public repositories.", + "urls": [ + { + "name": "Core Protocol", + "url": "https://github.com/sky-ecosystem/dss", + "type": "github" + }, + { + "name": "Contract Registry", + "url": "https://github.com/sky-ecosystem/spells-mainnet/blob/master/src/test/addresses_mainnet.sol", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "Aragon has not been able to verify the concentration of holdings.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "unevaluated", + "notes": "Aragon has not been able to verify the concentration of holdings.", + "evidence": [] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "unevaluated", + "notes": "Vesting contracts exist but specific unlock schedules were not enumerated in this analysis.", + "evidence": [ + { + "name": "Vesting Contracts", + "summary": "Vesting contracts exist for token distributions. Governance controls these schedules.", + "urls": [ + { + "name": "Vest Contract", + "url": "https://etherscan.io/address/0x67eaDb3288cceDe034cE95b0511DCc65cf630bB6#code", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Current Sky branding is not controlled by SKY tokenholders. Skybase International's terms reserve trademarks, service marks, logos, and trade names associated with the Services, including the Sky name, to the service operator or its licensors, and the DAI Foundation independently holds the legacy Maker and DAI trademarks. All code is open source (AGPL-3.0).", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Official Skybase International terms state that trademarks, service marks, logos, and trade names associated with the Services are proprietary to Skybase International or its licensors, and explicitly include the Sky name. Aragon has not found evidence that current Sky / USDS branding is owned by a tokenholder-controlled legal entity. Separately, the DAI Foundation holds the legacy Maker and DAI trademarks outside token governance.", + "evidence": [ + { + "name": "Skybase International Terms of Use", + "summary": "Skybase International's terms say trademarks, service marks, logos, and trade names associated with the Services are proprietary to Skybase International or its licensors, and explicitly include the Sky name.", + "urls": [ + { + "name": "Terms of Use", + "url": "https://docs.sky.money/legal/skybase-international/terms-of-use", + "type": "docs" + } + ] + }, + { + "name": "DAI Foundation", + "summary": "Holds the legacy Maker and DAI trademarks. Operates under Danish law, independent of token governance.", + "urls": [ + { + "name": "Foundation Website", + "url": "https://daifoundation.org", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "unevaluated", + "notes": "Domain ownership not verified. Open source frontends exist that anyone can deploy.", + "evidence": [ + { + "name": "Open Source Frontends", + "summary": "Multiple open source frontends exist. Anyone can deploy their own interface.", + "urls": [ + { + "name": "Governance Portal Source", + "url": "https://github.com/sky-ecosystem/governance-portal-v2", + "type": "github" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "positive", + "notes": "All code is AGPL-3.0 licensed. Anyone can fork and deploy the protocol. The license requires source disclosure.", + "evidence": [ + { + "name": "AGPL-3.0 License", + "summary": "Copyleft license. Anyone can fork and deploy, but must share source code of modifications.", + "urls": [ + { + "name": "License File", + "url": "https://github.com/sky-ecosystem/sky/blob/master/LICENSE", + "type": "github" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/uni.json b/src/data/generated/tokens/uni.json new file mode 100644 index 0000000..c940a76 --- /dev/null +++ b/src/data/generated/tokens/uni.json @@ -0,0 +1,666 @@ +{ + "id": "uni", + "coingeckoId": "uniswap", + "name": "UNI", + "symbol": "UNI", + "address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + "icon": "https://assets.coingecko.com/coins/images/12504/standard/uniswap-logo.png", + "description": "UNI is the governance token of the Uniswap DAO, the DAO governing the Uniswap protocol - the largest decentralised exchange in DeFi.", + "network": "ethereum", + "lastUpdated": 1775651992, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://uniswap.org/", + "twitter": "https://twitter.com/uniswap", + "scan": "https://etherscan.io/token/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" + }, + "infoDescription": "Uniswap is the largest onchain marketplace. Buy and sell crypto on Ethereum and 16+ other chains.", + "positive": 13, + "neutral": 3, + "atRisk": 0, + "evidenceEntries": 25, + "score": { + "tokenId": "uni", + "passing": 13, + "total": 13, + "percentage": 100, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 3, + "total": 3, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 1, + "total": 1, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 3, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "positive", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "unevaluated", + "distribution__supply-schedule": "positive", + "offchain__trademark": "warning", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "UNI holders maintain ultimate control through onchain voting. The token is immutable with a hardcoded 2% annual inflation cap. All core contracts (V2, V3, V4) are non-upgradeable with no pause/freeze functions.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "UNI holders vote onchain via GovernorBravoDelegator, with execution through Timelock after a time delay.", + "evidence": [ + { + "urls": [ + { + "name": "GovernorBravoDelegator", + "url": "https://etherscan.io/address/0x408ed6354d4973f66138c91495f2f2fcbd8724c3#code", + "type": "explorer" + }, + { + "name": "Timelock", + "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "All critical roles flow through governance-controlled contracts, ensuring UNI holders maintain ultimate control.", + "evidence": [ + { + "name": "V2 Factory", + "summary": "feeToSetter is Timelock. Only feeToSetter can call setFeeTo() to change fee destination.", + "urls": [ + { + "name": "V2Factory", + "url": "https://etherscan.io/address/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f#code", + "type": "explorer" + } + ] + }, + { + "name": "V3 Factory", + "summary": "Owner is V3FeeAdapter, whose feeSetter is Timelock. Only Timelock can change V3 protocol fees.", + "urls": [ + { + "name": "V3Factory", + "url": "https://etherscan.io/address/0x1f98431c8ad98523631ae4a59f267346ea31f984#code", + "type": "explorer" + } + ] + }, + { + "name": "V4 PoolManager", + "summary": "Owner is Timelock. Only owner can call setProtocolFeeController().", + "urls": [ + { + "name": "V4PoolManager", + "url": "https://etherscan.io/address/0x000000000004444c5dc75cB358380D2e3dE08A90#code", + "type": "explorer" + } + ] + }, + { + "name": "UNI Token", + "summary": "Minter is set to Timelock. Since Timelock is controlled by GovernorBravoDelegator, all minting operations are controlled by UNI holders.", + "urls": [ + { + "name": "UNI Token", + "url": "https://etherscan.io/address/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984#code", + "type": "explorer" + }, + { + "name": "Timelock (deployed minter)", + "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "All Uniswap protocol contracts (V2 Pairs, V3 Pools, V4 PoolManager, Timelock) are non-upgradeable. Only GovernorBravoDelegator is upgradeable, controlled by Timelock.", + "evidence": [ + { + "name": "Non-upgradeable Protocol Contracts", + "summary": "V2 Pairs use Create2 deployment. V3 Pools use NoDelegateCall protection. V4 PoolManager uses Singleton design. All are immutable once deployed.", + "urls": [ + { + "name": "V2 Factory Create2", + "url": "https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol#L28-L31", + "type": "github" + }, + { + "name": "V3 Pool Protection", + "url": "https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L30", + "type": "github" + }, + { + "name": "V4 PoolManager: Singleton", + "url": "https://github.com/Uniswap/v4-core/blob/main/src/PoolManager.sol#L93", + "type": "github" + } + ] + }, + { + "name": "Upgradeable Governance Contract", + "summary": "GovernorBravoDelegator is upgradeable, with the Admin role held by the Timelock. Any upgrade would require a governance vote by UNI holders.", + "urls": [ + { + "name": "GovernorBravoDelegator", + "url": "https://etherscan.io/address/0x408ed6354d4973f66138c91495f2f2fcbd8724c3#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "Token contract is immutable with no proxy patterns, no delegatecall, no EIP-1967/UUPS patterns.", + "evidence": [ + { + "urls": [ + { + "name": "Uni.sol Source", + "url": "https://github.com/Uniswap/governance/blob/master/contracts/Uni.sol#L6", + "type": "github" + }, + { + "name": "Etherscan", + "url": "https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "UNI has a hardcoded 2% annual inflation cap that cannot be changed. Minting is controlled by Timelock (hence UNI holders).", + "evidence": [ + { + "name": "Inflation Cap", + "summary": "Mint can be called once per year, minting maximum 2% of total supply. mintCap is constant and cannot be changed.", + "urls": [ + { + "name": "Mint once per year logic", + "url": "https://github.com/Uniswap/governance/blob/eabd8c71ad01f61fb54ed6945162021ee419998e/contracts/Uni.sol#L116", + "type": "github" + }, + { + "name": "2% mint cap", + "url": "https://github.com/Uniswap/governance/blob/eabd8c71ad01f61fb54ed6945162021ee419998e/contracts/Uni.sol#L120", + "type": "github" + } + ] + }, + { + "name": "Minting Control", + "summary": "Minter is set to Timelock, governed by UNI token holders through GovernorBravoDelegator.", + "urls": [ + { + "name": "Timelock", + "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC#code", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "positive", + "notes": "No pause/freeze functions in V2 Pair, V3 Pool, or V4 PoolManager core contracts. No admin backdoors to steal funds. No emergency withdrawal or sweep functions.", + "evidence": [] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "No blacklist, whitelist, or pause functions in UNI token contract. Transfers cannot be censored. Standard ERC20 with no admin controls over transfers.", + "evidence": [ + { + "urls": [ + { + "name": "Uni.sol", + "url": "https://github.com/Uniswap/governance/blob/master/contracts/Uni.sol#L6", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "Protocol fees flow to governance-controlled destinations through TokenJar and FirePit burn mechanism. Treasury is entirely controlled by token holders.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "Protocol fees across all Uniswap versions flow to governance-controlled destinations (TokenJar). Value accrual is via burn mechanism - a burn can be initiated by anyone who chooses to burn 4,000 UNI to claim accumulated protocol fees from TokenJar via FirePit.", + "evidence": [ + { + "name": "V2 Fees", + "summary": "All V2 pools are active. Fees go to FeeTo address which is set to TokenJar.", + "urls": [ + { + "name": "UniswapV2Pool: feeTo", + "url": "https://etherscan.io/address/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f#readContract", + "type": "explorer" + }, + { + "name": "UniswapV2Pool: Fee Destination", + "url": "https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L90", + "type": "github" + } + ] + }, + { + "name": "V3 Fees", + "summary": "V3FeeAdapter collects protocol fees by calling collectProtocol and sends them to TokenJar. The 4,000 UNI threshold can be changed by governance via Timelock in FirePit. Only select V3 pools are currently active.", + "urls": [ + { + "name": "V3FeeAdapter", + "url": "https://etherscan.io/address/0x5E74C9f42EEd283bFf3744fBD1889d398d40867d#code", + "type": "explorer" + }, + { + "name": "UniswapV3Pool: collectProtocol", + "url": "https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L848", + "type": "github" + } + ] + }, + { + "name": "V4 Fees", + "summary": "V4 fees have not been activated yet, but in the future fees can be activated via governance process as described in Onchain Governance Workflow. To activate fees, Timelock will set the PoolManager's ProtocolFeeController address to a V4FeeAdapter contract which contains logic for fee collection and distribution.", + "urls": [ + { + "name": "V4 PoolManager", + "url": "https://etherscan.io/address/0x000000000004444c5dc75cB358380D2e3dE08A90#code", + "type": "explorer" + }, + { + "name": "V4 PoolManager: CollectProtocolFees", + "url": "https://github.com/Uniswap/v4-core/blob/d153b048868a60c2403a3ef5b2301bb247884d46/src/ProtocolFees.sol#L48", + "type": "github" + } + ] + }, + { + "name": "Burn Mechanism", + "summary": "The 4,000 UNI threshold can be changed by governance via Timelock in FirePit.", + "urls": [ + { + "name": "FirePit 4000 UNI Requirement", + "url": "https://github.com/Uniswap/protocol-fees/blob/8604e4b9aed88bdd6be3a322e19722c40f94be2c/src/releasers/ExchangeReleaser.sol#L35", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "positive", + "notes": "The Uniswap Governance Timelock, often referred to as the Treasury, is entirely controlled by token holders. At the time of writing, it held approximately 264m UNI tokens. Funds held in Timelock require UNI governance proposals to execute transfers, ensuring tokenholder control.", + "evidence": [ + { + "urls": [ + { + "name": "Timelock", + "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "Token holders control fee routing and burn threshold across all versions through Timelock-governed ownership.", + "evidence": [ + { + "name": "V2 Fee Control", + "summary": "feeToSetter is set to Timelock on UniswapV2Factory. Only feeToSetter can call setFeeTo to change the fee destination. Protocol fees can only be enabled or disabled, not adjusted. Protocol fees on V2 pools cannot be adjusted on a per pool basis.", + "urls": [ + { + "name": "Timelock", + "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC", + "type": "explorer" + }, + { + "name": "UniswapV2Factory", + "url": "https://etherscan.io/address/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f", + "type": "explorer" + } + ] + }, + { + "name": "V3 Fee Control", + "summary": "Owner of UniswapV3Factory is V3FeeAdapter, whose owner is Timelock. V3FeeAdapter's owner can call setOwner on V3FeeAdapter, which passes the call through to UniswapV3Factory. Using this method, V3FeeAdapter's owner can change the owner of the UniswapV3Factory to a new V3FeeAdapter. The pools collecting protocol fees as well as the protocol fee percentage per pool can be changed by the pool owner via setFeeProtocol", + "urls": [ + { + "name": "UniswapV3Factory", + "url": "https://etherscan.io/address/0x1f98431c8ad98523631ae4a59f267346ea31f984", + "type": "explorer" + }, + { + "name": "UniswapV3Pool: setFeeProtocol", + "url": "https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L837", + "type": "github" + } + ] + }, + { + "name": "V4 Fee Control", + "summary": "Owner of V4PoolManager is set to Timelock. Only owner can call setProtocolFeeController to change the fee controller. Protocol fees can be modified by the ProtocolFeeController through setProtocolFee.", + "urls": [ + { + "name": "PoolManager(V4): setProtocolFee", + "url": "https://github.com/Uniswap/v4-core/blob/d153b048868a60c2403a3ef5b2301bb247884d46/src/ProtocolFees.sol#L35", + "type": "github" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "DUNI (Decentralized Unincorporated Nonprofit Association) provides legal capacity for the DAO to engage in off-chain activities such as tax compliance, legal defense, and contract execution. At the time of writing, there are no specific offchain value accrual mechanisms explicitly defined.", + "evidence": [ + { + "urls": [ + { + "name": "DUNI Proposal", + "url": "https://gov.uniswap.org/t/governance-proposal-establish-uniswap-governance-as-duni-a-wyoming-duna/25770", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "UNI token source is publicly available on GitHub and verified on Etherscan. Uniswap V2, V3, V4, and UniswapX protocol contracts are open source and verified.", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "The UNI token contract source code (Uni.sol) is publicly available on GitHub and verified on Etherscan.", + "evidence": [ + { + "urls": [ + { + "name": "UNI Token (Etherscan)", + "url": "https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984#code", + "type": "explorer" + }, + { + "name": "Uni.sol Source (GitHub)", + "url": "https://github.com/Uniswap/governance/blob/master/contracts/Uni.sol", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "Uniswap V2, V3, V4, and UniswapX protocol contracts are open source on GitHub.", + "evidence": [ + { + "urls": [ + { + "name": "Uniswap V2 Core (GitHub)", + "url": "https://github.com/Uniswap/v2-core", + "type": "github" + }, + { + "name": "Uniswap V3 Core (GitHub)", + "url": "https://github.com/Uniswap/v3-core", + "type": "github" + }, + { + "name": "Uniswap V4 Core (GitHub)", + "url": "https://github.com/Uniswap/v4-core", + "type": "github" + }, + { + "name": "UniswapX (GitHub)", + "url": "https://github.com/Uniswap/UniswapX", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "unevaluated", + "notes": "Aragon has not yet verified that 3rd parties do not hold more than 50% of voting power", + "evidence": [] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "positive", + "notes": "Four vesting contracts distributed UNI to the Governance Timelock over four years, ending in September 2024. There are no future unlocks.", + "evidence": [ + { + "name": "TreasuryVester Contracts", + "summary": "UNI tokens were initially minted to an EOA, which transferred the Treasury's portion to four vesting contracts in varying amounts. Each TreasuryVester contract was configured with: uni, recipient, vestingAmount, vestingBegin, vestingCliff, vestingEnd.", + "urls": [ + { + "name": "TreasuryVester Code", + "url": "https://github.com/Uniswap/governance/blob/master/contracts/TreasuryVester.sol", + "type": "github" + }, + { + "name": "TreasuryVester 1", + "url": "https://etherscan.io/address/0x4750c43867ef5f89869132eccf19b9b6c4286e1a", + "type": "explorer" + }, + { + "name": "TreasuryVester 2", + "url": "https://etherscan.io/address/0xe3953d9d317b834592ab58ab2c7a6ad22b54075d", + "type": "explorer" + }, + { + "name": "TreasuryVester 3", + "url": "https://etherscan.io/address/0x4b4e140d1f131fdad6fb59c13af796fd194e4135", + "type": "explorer" + }, + { + "name": "TreasuryVester 4", + "url": "https://etherscan.io/address/0x3d30b1ab88d487b0f3061f40de76845bec3f1e94", + "type": "explorer" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Core trademark rights (UNISWAP, UNI, UNISWAP LABS) are held by Universal Navigation Inc. (Uniswap Labs). DUNI has a limited, non-exclusive trademark license but no ownership transfer has occurred. Uniswap Labs operates a user interface and API, but the permissionless smart contracts allow anybody to operate one.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Core trademark rights related to Uniswap Labs, including UNISWAP, UNI, and UNISWAP LABS, are held and enforceable by Universal Navigation Inc. d/b/a Uniswap Labs (\"Labs\").\n\nAfter the passing of the UNIfication proposal, in which Uniswap Labs entered into a service provider agreement with DUNI (formerly Uniswap Governance), Labs grants DUNI a non-exclusive, royalty-free, worldwide trademark license to use the UNISWAP mark solely in connection with DUNI's business, subject to Labs' trademark usage guidelines and limited to the term of the agreement.\n\nThe agreement does not transfer ownership of the UNISWAP mark or Labs' other trademarks to DUNI or token holders.\n\nAny broader assignment, or permanent or exclusive license, of Labs' trademarks to DUNI is explicitly deferred and would require a separate written agreement negotiated in the future.", + "evidence": [ + { + "urls": [ + { + "name": "Uniswap Labs Trademark Guidelines", + "url": "https://support.uniswap.org/hc/en-us/articles/30934762216973-Uniswap-Labs-Trademark-Guidelines/", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Uniswap Labs operates a user interface and API, both of which are subject to Terms of Service. The permissionless nature of the Uniswap Protocol’s smart contracts means that any person or firm can operate an interface or API.", + "evidence": [ + { + "urls": [ + { + "name": "Uniswap Labs Terms of Service", + "url": "https://support.uniswap.org/hc/en-us/articles/30935100859661-Uniswap-Labs-Terms-of-Service", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Uniswap Labs retains ownership of its pre-existing IP and trademarks. DUNI holds a limited, non-exclusive license to use the UNISWAP trademark for DUNI-related purposes, and generally receives rights to new deliverables under open-source licenses. No transfer of core Labs' IP has occurred; any broader licensing or assignment would require a separate agreement.", + "evidence": [] + } + ] + } + ] +} diff --git a/src/data/generated/tokens/yb.json b/src/data/generated/tokens/yb.json new file mode 100644 index 0000000..9ae11be --- /dev/null +++ b/src/data/generated/tokens/yb.json @@ -0,0 +1,635 @@ +{ + "id": "yb", + "coingeckoId": "yield-basis", + "name": "YB", + "symbol": "YB", + "address": "0x01791F726B4103694969820be083196cC7c045fF", + "icon": "https://coin-images.coingecko.com/coins/images/54871/small/yieldbasis_400x400.png", + "description": "YB is the token of YieldBasis. veYB holders control protocol governance through Aragon, direct YB emissions via gauge voting, and receive protocol admin fees.", + "network": "ethereum", + "lastUpdated": 1774549304, + "updatedBy": { + "avatar": "/logo192.png", + "name": "Aragon Developers" + }, + "links": { + "website": "https://yieldbasis.com/", + "twitter": "https://x.com/yieldbasis", + "scan": "https://etherscan.io/token/0x01791F726B4103694969820be083196cC7c045fF" + }, + "infoDescription": "Yield Basis is the liquidity protocol designed to eliminate Impermanent Loss (IL) in AMMs using constantly-maintained 2x leveraged liquidity provision.", + "positive": 11, + "neutral": 4, + "atRisk": 1, + "evidenceEntries": 18, + "score": { + "tokenId": "yb", + "passing": 11, + "total": 14, + "percentage": 78.57142857142857, + "metrics": [ + { + "metricId": "onchain-ctrl", + "metricName": "Onchain Control", + "passing": 7, + "total": 7, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "val-accrual", + "metricName": "Value Accrual", + "passing": 2, + "total": 3, + "percentage": 66.66666666666666, + "evaluated": true, + "reference": false + }, + { + "metricId": "verifiability", + "metricName": "Verifiability", + "passing": 2, + "total": 2, + "percentage": 100, + "evaluated": true, + "reference": false + }, + { + "metricId": "distribution", + "metricName": "Token Distribution", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": true, + "reference": false + }, + { + "metricId": "offchain", + "metricName": "Offchain Dependencies", + "passing": 0, + "total": 2, + "percentage": 0, + "evaluated": false, + "reference": true + } + ] + }, + "criteriaStatuses": { + "onchain-ctrl__governance-workflow": "positive", + "onchain-ctrl__role-accountability": "positive", + "onchain-ctrl__protocol-upgrade": "positive", + "onchain-ctrl__token-upgrade": "positive", + "onchain-ctrl__supply": "positive", + "onchain-ctrl__access-gating": "positive", + "onchain-ctrl__censorship": "positive", + "val-accrual__active": "positive", + "val-accrual__treasury": "at_risk", + "val-accrual__mechanism": "positive", + "val-accrual__offchain": "unevaluated", + "verifiability__token-source": "positive", + "verifiability__protocol-source": "positive", + "distribution__concentration": "warning", + "distribution__supply-schedule": "warning", + "offchain__trademark": "unevaluated", + "offchain__distribution": "warning", + "offchain__licensing": "warning" + }, + "metrics": [ + { + "id": "onchain-ctrl", + "name": "Onchain Control", + "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit. Concretely, it maps who can upgrade core logic, change parameters, invoke emergency actions, modify token behavior or supply, freeze/blacklist/seize/force-transfer assets, or limit protocol actions and exit paths.", + "summary": "veYB holders control the protocol through Aragon governance. The YB token is non-upgradeable with renounced ownership. Protocol upgrades are controlled by the DAO through MigrationFactoryOwner. The veYB contract owner (_yb.eth) can change transfer clearance rules, but this does not constitute censorshipβ€”users remain custodians of their locked YB.", + "tags": [ + "Metric 1" + ], + "criteria": [ + { + "id": "onchain-ctrl__governance-workflow", + "name": "Onchain Governance Workflow", + "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", + "status": "positive", + "notes": "veYB holders vote onchain via Aragon governance DAO. Proposals require **30% participation**, **55% support**, and a **7-day voting period**. Proposals can be executed before the full voting period concludes when mathematical certainty is achieved (thresholds met, remaining votes cannot change outcome). Minimum 1 veYB required to create proposals.", + "evidence": [ + { + "name": "Governance System", + "summary": "Aragon governance DAO with TokenVoting Plugin. veYB implements standard IVotes interface for Aragon compatibility.", + "urls": [ + { + "name": "DAO Contract", + "url": "https://etherscan.io/address/0x42F2A41A0D0e65A440813190880c8a65124895Fa", + "type": "explorer" + }, + { + "name": "veYB (VotingEscrow)", + "url": "https://etherscan.io/address/0x8235c179E9e84688FBd8B12295EfC26834dAC211", + "type": "explorer" + }, + { + "name": "TokenVoting Plugin", + "url": "https://etherscan.io/address/0x2be6670DE1cCEC715bDBBa2e3A6C1A05E496ec78", + "type": "explorer" + }, + { + "name": "Governance Documentation", + "url": "https://docs.yieldbasis.com/user/governance", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__role-accountability", + "name": "Role Accountability", + "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", + "status": "positive", + "notes": "**DAO-controlled:** GaugeController, FeeDistributor, MigrationFactoryOwner (ADMIN immutable = DAO).\n\n**EOA-controlled:** veYB owner (_yb.eth) can change transfer clearance rules. However, transfer restrictions do not constitute censorshipβ€”users remain custodians of their locked YB and can always withdraw after lock expiry.", + "evidence": [ + { + "name": "DAO-Controlled Contracts", + "summary": "GaugeController.owner() = DAO. FeeDistributor.owner() = DAO. MigrationFactoryOwner.ADMIN = DAO (immutable).", + "urls": [ + { + "name": "GaugeController", + "url": "https://etherscan.io/address/0x1Be14811A3a06F6aF4fA64310a636e1Df04c1c21", + "type": "explorer" + }, + { + "name": "FeeDistributor", + "url": "https://etherscan.io/address/0xD11b416573EbC59b6B2387DA0D2c0D1b3b1F7A90", + "type": "explorer" + }, + { + "name": "MigrationFactoryOwner", + "url": "https://etherscan.io/address/0xa68343ed4d517a277cfa1f2fc2b51f7a6794b6ad", + "type": "explorer" + } + ] + }, + { + "name": "veYB Owner (_yb.eth)", + "summary": "Can call set_transfer_clearance_checker() to change veYB transfer rules. This restricts veNFT transfers, NOT custodyβ€”users remain owners of their locked YB.", + "urls": [ + { + "name": "veYB Contract", + "url": "https://etherscan.io/address/0x8235c179E9e84688FBd8B12295EfC26834dAC211", + "type": "explorer" + }, + { + "name": "set_transfer_clearance_checker", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/VotingEscrow.vy#L640-L647", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__protocol-upgrade", + "name": "Protocol Upgrade Authority", + "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", + "status": "positive", + "notes": "The DAO controls protocol upgrades through MigrationFactoryOwner. This constitutes ownershipβ€”the indirection through governance is the standard mechanism for tokenholder control.", + "evidence": [ + { + "name": "Upgrade Path", + "summary": "Factory.set_implementations() requires Factory admin = MigrationFactoryOwner. MigrationFactoryOwner requires ADMIN (immutable) = DAO.", + "urls": [ + { + "name": "Factory Contract", + "url": "https://etherscan.io/address/0x370a449FeBb9411c95bf897021377fe0B7D100c0", + "type": "explorer" + }, + { + "name": "Factory.set_implementations", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/Factory.vy#L389-L410", + "type": "github" + }, + { + "name": "MigrationFactoryOwner ADMIN immutable", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/MigrationFactoryOwner.vy#L47", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__token-upgrade", + "name": "Token Upgrade Authority", + "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", + "status": "positive", + "notes": "YB token is a **non-upgradeable** Vyper contract. Ownership has been **renounced** (owner = 0x0). No proxy pattern. Only GaugeController (set before renouncement) can mint via emit().", + "evidence": [ + { + "name": "Token Ownership", + "summary": "owner() returns 0x0. Token uses standard ERC-20 implementation with renounced ownership at deployment.", + "urls": [ + { + "name": "YB Token Contract", + "url": "https://etherscan.io/address/0x01791F726B4103694969820be083196cC7c045fF#code", + "type": "explorer" + }, + { + "name": "renounce_ownership", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy#L90-L100", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__supply", + "name": "Supply Control", + "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", + "status": "positive", + "notes": "**1B max supply** with programmatic emission. Only GaugeController can mint. Emission rate determined by gauge staking levels: get_adjustment() returns sqrt(staked / totalSupply), so higher LP staking = higher emission rate (up to 100% of max rate).", + "evidence": [ + { + "name": "Emission Mechanism", + "summary": "GaugeController calls YB.emit() with rate_factor based on LP staking levels. No discretionary minting possible.", + "urls": [ + { + "name": "get_adjustment (minting rate)", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/LiquidityGauge.vy#L133-L142", + "type": "github" + }, + { + "name": "YB.emit function", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy#L103-L122", + "type": "github" + }, + { + "name": "Tokenomics", + "url": "https://docs.yieldbasis.com/user/tokenomics", + "type": "docs" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__access-gating", + "name": "Privileged Access Gating", + "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", + "status": "positive", + "notes": "User exits are **permissionless**: veYB withdrawal after lock expiry, fee claims. Gauge killing is DAO-controlled and affects emissions, not user funds.", + "evidence": [ + { + "name": "Exit Paths", + "summary": "VotingEscrow.withdraw() permissionless after lock expiry. FeeDistributor.claim() has no admin check.", + "urls": [ + { + "name": "VotingEscrow.withdraw", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/VotingEscrow.vy#L428-L457", + "type": "github" + }, + { + "name": "FeeDistributor.claim", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/FeeDistributor.vy#L269-L277", + "type": "github" + } + ] + } + ] + }, + { + "id": "onchain-ctrl__censorship", + "name": "Token Censorship", + "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", + "status": "positive", + "notes": "**No blacklist, freeze, or seizure functions** in YB token. The veYB transfer clearance checker can restrict veNFT transfers, but this does not censor the underlying YB tokenβ€”users remain custodians and can withdraw after lock expiry.", + "evidence": [ + { + "urls": [ + { + "name": "YB.vy full source", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy", + "type": "github" + } + ] + } + ] + } + ] + }, + { + "id": "val-accrual", + "name": "Value Accrual", + "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations. It focuses on whether a real value engine exists and is active.", + "summary": "veYB holders receive **protocol admin fees** (a subset of protocol revenue) via FeeDistributor. Fees originate from LT (Liquidity Token) vault admin fees, distributed weekly in yb-LP tokens. DAO controls fee direction via MigrationFactoryOwner, and gauge voting directs YB emissions. The Ecosystem Reserve is controlled by an EOA, not the DAO.", + "tags": [ + "Metric 2" + ], + "criteria": [ + { + "id": "val-accrual__active", + "name": "Accrual Active", + "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", + "status": "positive", + "notes": "FeeDistributor is **actively distributing** yb-cbBTC, yb-WBTC, yb-tBTC, and yb-WETH LP tokens to veYB holders.\n\n**Important distinction:** Protocol revenue = LP fees + position rebalancing expenses. What veYB holders receive is the **protocol admin fee** (admin_fee)β€”a subset of protocol revenue, not the total.\n\n**Fee flow:** LT vaults accrue admin fees β†’ anyone calls withdraw_admin_fees() β†’ mints LT to fee_receiver (FeeDistributor) β†’ fill_epochs() distributes over 4 weeks β†’ veYB holders claim pro-rata.\n\n**Data source:** [ValueVerse](https://yb.valueverse.ai)", + "evidence": [ + { + "name": "Fee Flow", + "summary": "Admin fees from LT vaults flow to FeeDistributor via Factory.fee_receiver. Distribution is automatic and weekly over 4 epochs.", + "urls": [ + { + "name": "LT.withdraw_admin_fees", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/LT.vy#L866-L896", + "type": "github" + }, + { + "name": "Factory.fee_receiver", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/Factory.vy#L98", + "type": "github" + }, + { + "name": "FeeDistributor._fill_epochs (OVER_WEEKS=4)", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/FeeDistributor.vy#L86-L107", + "type": "github" + }, + { + "name": "FeeDistributor Contract", + "url": "https://etherscan.io/address/0xD11b416573EbC59b6B2387DA0D2c0D1b3b1F7A90", + "type": "explorer" + }, + { + "name": "veYB Documentation", + "url": "https://docs.yieldbasis.com/user/veyb", + "type": "docs" + }, + { + "name": "Fee Data (ValueVerse)", + "url": "https://yb.valueverse.ai", + "type": "docs" + } + ] + } + ] + }, + { + "id": "val-accrual__treasury", + "name": "Treasury Ownership", + "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", + "status": "at_risk", + "notes": "The **Ecosystem Reserve** is a VestingEscrow controlled by an EOA, not the DAO. This is the largest pool of discretionary YB outside the vesting contracts. The DAO holds a small amount of YB directly.", + "evidence": [ + { + "name": "Ecosystem Reserve", + "summary": "Ecosystem Reserve is a VestingEscrow controlled by an EOA, not the DAO.", + "urls": [ + { + "name": "Ecosystem Reserve", + "url": "https://etherscan.io/address/0x7aC5922776034132D9ff5c7889d612d98e052Cf2", + "type": "explorer" + }, + { + "name": "Ecosystem Reserve Owner (EOA)", + "url": "https://etherscan.io/address/0xC1671c9efc9A2ecC347238BeA054Fc6d1c6c28F9", + "type": "explorer" + }, + { + "name": "DAO Contract", + "url": "https://etherscan.io/address/0x42F2A41A0D0e65A440813190880c8a65124895Fa", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__mechanism", + "name": "Accrual Mechanism Control", + "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", + "status": "positive", + "notes": "veYB holders control both the **direction of automated value flows** and **emission routing**:\n\n**Fee direction:** DAO controls Factory.fee_receiver via MigrationFactoryOwner, determining where admin fees are routed.\n\n**Gauge voting = fee control by veYB:** veYB holders vote on gauge weights. Higher weight = more YB emissions to that pool's stakers, incentivizing LP staking which generates the admin fees distributed back to veYB holders. 10-day vote lock prevents manipulation.", + "evidence": [ + { + "name": "Fee Flow Direction", + "summary": "DAO controls Factory.fee_receiver via MigrationFactoryOwner.set_fee_receiver(). This determines where admin fees are routed.", + "urls": [ + { + "name": "MigrationFactoryOwner.set_fee_receiver", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/MigrationFactoryOwner.vy#L143-L145", + "type": "github" + }, + { + "name": "Factory.set_fee_receiver", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/Factory.vy#L358-L364", + "type": "github" + } + ] + }, + { + "name": "Gauge Voting", + "summary": "veYB holders vote on gauge weights to direct YB emissions. This incentivizes LP staking, generating admin fees that flow back to veYB holders.", + "urls": [ + { + "name": "vote_for_gauge_weights", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/GaugeController.vy#L206-L287", + "type": "github" + }, + { + "name": "GaugeController Contract", + "url": "https://etherscan.io/address/0x1Be14811A3a06F6aF4fA64310a636e1Df04c1c21", + "type": "explorer" + } + ] + } + ] + }, + { + "id": "val-accrual__offchain", + "name": "Offchain Value Accrual", + "about": "Are there additional offchain value accrual flows that benefit tokenholders?", + "status": "unevaluated", + "notes": "Aragon has not been able to verify whether YieldBasis AG (operating entity) provides any offchain value to YB token holders. No evidence of equity linkage, offchain revenue sharing, or legal commitments to tokenholders.", + "tags": [ + "Reference" + ], + "evidence": [] + } + ] + }, + { + "id": "verifiability", + "name": "Verifiability", + "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances. In practice, it answers: what code is running, where it is deployed, and whether the deployed bytecode can be credibly matched to publicly available source (including proxy implementations and build inputs where relevant).", + "summary": "All core contracts are verified on Etherscan with matching GitHub source (Vyper 0.4.3). Protocol has undergone 6 independent security audits (Statemind, Chainsecurity, Quantstamp, Mixbytes, Electisec, Pashov).", + "tags": [ + "Metric 3" + ], + "criteria": [ + { + "id": "verifiability__token-source", + "name": "Token Contract Source Verification", + "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", + "status": "positive", + "notes": "YB token is **verified on Etherscan** (Vyper 0.4.3, AGPL v3.0 license). Source code available on GitHub with matching bytecode.", + "evidence": [ + { + "urls": [ + { + "name": "YB Token Verified on Etherscan", + "url": "https://etherscan.io/address/0x01791F726B4103694969820be083196cC7c045fF#code", + "type": "explorer" + }, + { + "name": "YB.vy Source", + "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy", + "type": "github" + } + ] + } + ] + }, + { + "id": "verifiability__protocol-source", + "name": "Protocol Component Source Verification", + "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", + "status": "positive", + "notes": "All core contracts (Factory, veYB, GaugeController, FeeDistributor) are **verified on Etherscan**. **6 independent security audits** completed between March-August 2025.", + "evidence": [ + { + "name": "Verification and Audits", + "summary": "Audited by Statemind (Feb-May 2025), Chainsecurity (Jul 2025), Quantstamp (Apr 2025), Mixbytes (Aug 2025), Electisec (Aug 2025), and Pashov (Mar-Apr 2025).", + "urls": [ + { + "name": "yb-core GitHub Repository", + "url": "https://github.com/yield-basis/yb-core", + "type": "github" + }, + { + "name": "Audit Reports", + "url": "https://docs.yieldbasis.com/user/audits-bug-bounties", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "distribution", + "name": "Token Distribution", + "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed. It measures whether any single actor or coordinated group under common control can form a controlling block large enough to determine or constrain tokenholder-governed outcomes.", + "summary": "~77M YB locked as veYB (~10% of minted supply). Team (25%) + Investors (12.1%) have 24-month vesting with a 6-month cliff ending March 2026, after which 25% unlocks and the remaining 75% vests linearly over 18 months.", + "tags": [ + "Metric 4" + ], + "criteria": [ + { + "id": "distribution__concentration", + "name": "Ownership Concentration", + "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", + "status": "warning", + "notes": "~77M veYB locked from ~722M minted YB (~10% participation). Post-cliff, **Team (250M) + Investors (121M) = 371M YB (37%)** could potentially influence governance if coordinated, given low current veYB participation.", + "evidence": [ + { + "name": "veYB Supply", + "summary": "veYB supply ~77M tokens locked. Low participation rate means large token holders have outsized influence.", + "urls": [ + { + "name": "veYB Contract", + "url": "https://etherscan.io/address/0x8235c179E9e84688FBd8B12295EfC26834dAC211", + "type": "explorer" + }, + { + "name": "Tokenomics", + "url": "https://docs.yieldbasis.com/user/tokenomics", + "type": "docs" + } + ] + } + ] + }, + { + "id": "distribution__supply-schedule", + "name": "Future Token Unlocks", + "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", + "status": "warning", + "notes": "**24-month vesting** with **6-month cliff** ending ~March 15, 2026.\n\n**During cliff:** Cannot sell, but **can lock into veYB**. ~35M YB was locked by team and investors during the cliff, choosing protocol fees over liquidity.\n\n**At cliff end:** 25% unlocks (6/24 months).\n\n**Post-cliff:** Remaining 75% vests linearly over 18 months until September 2027.\n\n**Affected:** Team (250M) + Investors (121M) = 371M YB (37% of max supply). Tokens release graduallyβ€”they cannot be instantly used to influence governance.", + "evidence": [ + { + "name": "Vesting Schedule", + "summary": "Start: Sept 15, 2025. Cliff: 6 months = March 15, 2026 (25% unlocks). Linear vesting: 18 months post-cliff (75% remaining). Full vest: Sept 15, 2027.", + "urls": [ + { + "name": "Tokenomics", + "url": "https://docs.yieldbasis.com/user/tokenomics", + "type": "docs" + } + ] + } + ] + } + ] + }, + { + "id": "offchain", + "name": "Offchain Dependencies", + "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", + "summary": "Aragon has not been able to verify trademark ownership. Aragon has not been able to identify published terms of service or a contracting entity for the primary distribution channels. DAO contracts use AGPL v3.0 (open source), but Factory uses proprietary license.", + "tags": [ + "Reference" + ], + "criteria": [ + { + "id": "offchain__trademark", + "name": "Trademark", + "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "unevaluated", + "notes": "Aragon has not been able to verify trademark ownership.", + "evidence": [] + }, + { + "id": "offchain__distribution", + "name": "Distribution", + "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "Aragon has not been able to identify published terms of service or a contracting entity for the primary distribution channels.", + "evidence": [ + { + "urls": [ + { + "name": "YieldBasis Website", + "url": "https://yieldbasis.com/", + "type": "docs" + } + ] + } + ] + }, + { + "id": "offchain__licensing", + "name": "Licensing", + "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", + "status": "warning", + "notes": "**Mixed licensing**: YB.vy, VotingEscrow.vy, GaugeController.vy use AGPL v3.0 (open source). **Factory.vy uses 'Copyright (c) 2025'** (proprietary).", + "evidence": [ + { + "name": "License Analysis", + "summary": "DAO contracts (YB, veYB, GaugeController, FeeDistributor): AGPL v3.0. Factory, MigrationFactoryOwner: 'Copyright (c) 2025' (proprietary).", + "urls": [ + { + "name": "yb-core contracts directory", + "url": "https://github.com/yield-basis/yb-core/tree/41137e5837e411c9d60be8705ca74304b082fa92/contracts", + "type": "github" + }, + { + "name": "Curve Licensing Vesting", + "url": "https://etherscan.io/address/0x36e36D5D588D480A15A40C7668Be52D36eb206A8", + "type": "explorer" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/data/metrics.json b/src/data/metrics.json deleted file mode 100644 index 25f20e5..0000000 --- a/src/data/metrics.json +++ /dev/null @@ -1,6195 +0,0 @@ -{ - "aave": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "AAVE, stkAAVE and aAAVE holders control the protocol through onchain governance with Timelock execution. The token has fixed 16M supply with no mint function. Delegated steward roles exist but are elected and revocable by governance.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "AAVE, stkAAVE and aAAVE holders vote onchain with execution through Timelock.", - "evidence": [ - { - "urls": [ - { - "name": "Governance Documentation", - "url": "https://aave.com/docs/ecosystem/governance", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "The Governance Emergency Guardian is a 5/9 multisig elected by holders to protect the protocol from governance takeover attacks by vetoing onchain payloads.", - "evidence": [ - { - "name": "Governance Emergency Guardian", - "urls": [ - { - "name": "Onchain address", - "url": "https://etherscan.io/address/0xCe52ab41C40575B072A18C9700091Ccbe4A06710#readProxyContract", - "type": "explorer" - }, - { - "name": "Detailed Governance Permissions", - "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#governance-v3-contracts", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "Holders control implementation upgrades for all v3 market deployments.", - "evidence": [ - { - "name": "Upgrade Documentation", - "summary": "Pool (upgradeable proxy) β†’ Admin is PoolAddressProvider β†’ onlyOwner is Executor β†’ owner is PayloadsController β†’ controlled by Governance.", - "urls": [ - { - "name": "Upgradability per contract", - "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#contracts-upgradeability", - "type": "github" - } - ] - }, - { - "name": "Ownership Chain", - "summary": "Pool β†’ PoolAddressProvider β†’ Executor β†’ PayloadsController β†’ Governance.\n\nFor PayloadsController to call Executor, MESSAGE_ORIGINATOR must equal Governance.", - "urls": [ - { - "name": "Pool", - "url": "https://etherscan.io/address/0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2#code", - "type": "explorer" - }, - { - "name": "Executor", - "url": "https://etherscan.io/address/0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A#code", - "type": "explorer" - }, - { - "name": "PayloadsController", - "url": "https://etherscan.io/address/0xdAbad81aF85554E9ae636395611C58F7eC1aAEc5#code", - "type": "explorer" - }, - { - "name": "Governance", - "url": "https://etherscan.io/address/0x9AEE0B04504CeF83A65AC3f0e838D0593BCb2BC7#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "The AAVE token is upgradeable but controlled by token holders.", - "evidence": [ - { - "name": "Ownership Chain", - "summary": "ERC-1967 Transparent proxy owned by ProxyAdmin, controlled by token holders.\n\nAAVE (Proxy) β†’ ProxyAdmin β†’ Executor β†’ PayloadsController β†’ Token Holders", - "urls": [ - { - "name": "AAVE Proxy", - "url": "https://etherscan.io/address/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9#readProxyContract", - "type": "explorer" - }, - { - "name": "ProxyAdmin", - "url": "https://etherscan.io/address/0x86c3FfEE349A7cFf7cA88C449717B1b133bfb517#code", - "type": "explorer" - }, - { - "name": "PayloadsController", - "url": "https://etherscan.io/address/0xdAbad81aF85554E9ae636395611C58F7eC1aAEc5#readProxyContract", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "Fixed 16m AAVE token supply. No mint() function or inflation pathway in the bytecode.", - "evidence": [ - { - "urls": [ - { - "name": "The only AAVE token minting transactions ever, amounting to 16m AAVE tokens", - "url": "https://etherscan.io/advanced-filter?tkn=0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9&txntype=2&fadd=0x0000000000000000000000000000000000000000", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "positive", - "notes": "Roles can be broadly classified into **Core Protocol Roles** and **Delegated Steward Roles**. AAVE holders elect and revoke all these roles with their standard governance procedure.", - "evidence": [ - { - "name": "Core", - "summary": "Core Protocol Roles: EMERGENCY_ADMIN, RISK_ADMIN, POOL_ADMIN, ASSET_LISTING_ADMIN. These live in ACLManager, owned by ACLAdmin (verifiable in PoolAddressesProvider). ACLAdmin is owned by PayloadsController, controlled by token holders.", - "urls": [ - { - "name": "Core Protocol Role addresses", - "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#admins", - "type": "github" - }, - { - "name": "Core Protocol Roles in ACLManager", - "url": "https://etherscan.io/address/0xc2aaCf6553D20d1e9d78E365AAba8032af9c85b0", - "type": "explorer" - }, - { - "name": "EMERGENCY_ADMIN_ROLE: GnosisSafeProxy", - "url": "https://etherscan.io/address/0x2cfe3ec4d5a6811f4b8067f0de7e47dfa938aa30#code", - "type": "explorer" - }, - { - "name": "POOL_ADMIN_ROLE: Executor", - "url": "https://etherscan.io/address/0x5300A1a15135EA4dc7aD5a167152C01EFc9b192A#code", - "type": "explorer" - } - ] - }, - { - "name": "Delegated Steward Roles", - "summary": "\n\nRisk Stewards β†’ Risk Parameters\nFinance Stewards β†’ Treasury\nAave Guardians β†’ Emergency", - "urls": [ - { - "name": "Stewards (docs)", - "url": "https://aave.com/help/governance/aave-community", - "type": "docs" - }, - { - "name": "Stewards (addresses)", - "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#contracts-upgradeability", - "type": "github" - } - ] - }, - { - "name": "Protocol Emergency Guardian", - "summary": "A separate multisig that executes limited emergency operations for the protocol.", - "urls": [ - { - "name": "Protocol Emergency Guardian", - "url": "https://aave.com/docs/ecosystem/governance#community-guardians-protocol-emergency-guardian", - "type": "docs" - }, - { - "name": "Detailed protocol permissions", - "url": "https://github.com/aave-dao/aave-permissions-book/blob/main/out/ETHEREUM-V3.md#contracts", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "No Guardian or blacklist capabilities exist in the AAVE token contract.", - "evidence": [ - { - "urls": [ - { - "name": "AAVE token's implementation code", - "url": "https://etherscan.io/address/0x5d4aa78b08bc7c530e21bf7447988b1be7991322#code", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "Protocol fees flow to a governance-controlled treasury. Holders can stake as stkAAVE in Safety Module for yield. Treasury composed of reserve factor from borrower interest + liquidation fees + flashloan premiums.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "Alongside the protocol fees going to the AAVE token-holder controlled treasury as an important source of value accrual, holders can stake their AAVE tokens as stkAAVE in Aave's Safety module, getting additional AAVE tokens as yield from the treasury for bearing the risk of slashing in case of protocol insolvency.\n\n**Caveat:** This Safety Module is in the transitional phase of being sunset in favour of aToken staking as part of the Umbrella release for automated bad debt coverage, which is live already.", - "evidence": [ - { - "urls": [ - { - "name": "Safety Module - Incentives", - "url": "https://aave.com/docs/aave-v3/concepts/incentives#safety-module", - "type": "docs" - }, - { - "name": "Umbrella Transition", - "url": "https://aave.com/help/umbrella/stake", - "type": "docs" - }, - { - "name": "Umbrella Activation Proposal", - "url": "https://vote.onaave.com/proposal/?proposalId=320", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "positive", - "notes": "AAVE holders control treasury usage. Composed of reserve factor (borrower interest), liquidation fees, and flashloan premiums.", - "evidence": [ - { - "name": "Treasury Control", - "summary": "Token holders direct treasury via governance.", - "urls": [ - { - "name": "Treasury docs", - "url": "https://aave.com/docs/aave-v3/concepts/incentives#incentives", - "type": "docs" - } - ] - }, - { - "name": "Fee Sources", - "summary": "Reserve factor (interest), liquidation fees, flashloan premiums all route to treasury via mintToTreasury().", - "urls": [ - { - "name": "mintToTreasury (PoolLogic)", - "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/libraries/logic/PoolLogic.sol#L105", - "type": "github" - }, - { - "name": "Flashloan premium", - "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/pool/Pool.sol#L653", - "type": "github" - }, - { - "name": "Liquidation fee", - "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/libraries/logic/LiquidationLogic.sol#L392", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "Token holders control fee levers (Reserve Factor, liquidation fees) via ACL-gated admin roles. Emergency actions are delegated, but economic controls are governance-owned through ACLManager.", - "evidence": [ - { - "name": "ACL Control", - "summary": "ACLManager gates admin roles (POOL_ADMIN, RISK_ADMIN) which control fee parameters. ACLManager itself is controlled by governance.", - "urls": [ - { - "name": "POOL_ADMIN role", - "url": "https://aave.com/docs/aave-v3/smart-contracts/acl-manager#roles-pool-admin", - "type": "docs" - }, - { - "name": "ACLManager", - "url": "https://github.com/aave-dao/aave-v3-origin/blob/main/src/contracts/protocol/configuration/ACLManager.sol#L45", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "tags": ["Reference"], - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "Aragon has not verified additional offchain value accrual flows to the AAVE token", - "evidence": [] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "AAVE token proxy and implementation are verified on Etherscan. Aave V3 protocol contracts are open source and verified.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The AAVE token contract source code is publicly available and verified on Etherscan.", - "evidence": [ - { - "urls": [ - { - "name": "AAVE token", - "url": "https://etherscan.io/address/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9#readProxyContract", - "type": "explorer" - }, - { - "name": "Aave V3 Origin (GitHub)", - "url": "https://github.com/aave-dao/aave-v3-origin", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "Aave V3 protocol contracts are open source on GitHub and verified on Etherscan.", - "evidence": [ - { - "urls": [ - { - "name": "Aave V3 Origin (GitHub)", - "url": "https://github.com/aave-dao/aave-v3-origin", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "Aragon has not verified if any 3rd party controls a majority share of the voting power of AAVE or derivatives.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "unevaluated", - "notes": "Aragon has not yet verified if any 3rd party controls a majority share of the voting power of AAVE or derivatives.", - "evidence": [] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "unevaluated", - "notes": "Aragon has not yet verified the supply schedule criteria for the AAVE token", - "evidence": [] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "US AAVE trademark is held by Quantum Swan OU (Estonia). Primary interface terms identify Aave Labs (a separate entity from the Aave DAO) as the contracting party.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "A US AAVE trademark filing lists Quantum Swan OU (Estonia) as the applicant/owner.", - "evidence": [ - { - "urls": [ - { - "name": "Trademark filing", - "url": "https://tsdr.uspto.gov/#caseNumber=79251290&caseSearchType=US_APPLICATION&caseType=DEFAULT&searchType=statusSearch", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "The primary interface terms identify Aave Labs as the contracting party.", - "evidence": [ - { - "urls": [ - { - "name": "Terms of Service", - "url": "https://aave.com/terms-of-service", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "unevaluated", - "notes": "Aragon has not yet verified all the licensing criteria associated with the AAVE token or the corresponding protocol and assets", - "evidence": [] - } - ] - } - ], - "aero": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "Aerodrome's core contracts are largely immutable with control of emissions given to veAERO holders via the gauge voting system. There are some minor areas in which privileged parties can control specific areas of the protocol, which are detailed below.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "veAERO holders control emissions through epoch-based gauge voting. An EmergencyCouncil exists for crisis handling with limited emergency powers.", - "evidence": [ - { - "name": "Voting System", - "summary": "AERO holders lock AERO in VotingEscrow to receive veAERO. veAERO holders vote each epoch on gauge weights via the Voter contract, and the Minter distributes emissions proportionally.", - "urls": [ - { - "name": "AERO Token", - "url": "https://basescan.org/address/0x940181a94A35A4569E4529A3CDfB74e38FD98631#code", - "type": "explorer" - }, - { - "name": "VotingEscrow", - "url": "https://basescan.org/address/0xeBf418Fe2512e7E6bd9b87a8F0f294aCDC67e6B4#code", - "type": "explorer" - }, - { - "name": "Voter", - "url": "https://basescan.org/address/0x16613524e02ad97eDfeF371bC883F2F5d6C480A5#code", - "type": "explorer" - }, - { - "name": "Minter", - "url": "https://basescan.org/address/0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5#code", - "type": "explorer" - } - ] - }, - { - "name": "Emergency Council", - "summary": "Exists to handle contingencies. It can kill or revive gauges, update pool name and symbol metadata, and rotate the EmergencyCouncil itself.", - "urls": [ - { - "name": "EmergencyCouncil", - "url": "https://aerodrome.limited/pages/security.html", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "veAERO holders indirectly control AERO emissions through epoch-based gauge voting executed by the Voter and Minter.", - "evidence": [ - { - "name": "Emergency Council", - "summary": "Exists but is not elected by veAERO holders. It holds limited emergency powers, including killing or reviving gauges and managing pool metadata. This role exists explicitly for crisis handling and sits outside direct tokenholder election.", - "urls": [ - { - "name": "Voter: emergencyCouncil", - "url": "https://basescan.org/address/0x16613524e02ad97eDfeF371bC883F2F5d6C480A5#readContract", - "type": "explorer" - }, - { - "name": "EmergencyCouncil", - "url": "https://basescan.org/address/0x99249b10593fCa1Ae9DAE6D4819F1A6dae5C013D#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "Core Aerodrome protocol contracts are non-upgradeable and do not use proxy patterns.", - "evidence": [ - { - "urls": [ - { - "name": "Aerodrome Contract Addresses", - "url": "https://aerodrome.finance/security", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "The AERO token contract is immutable and does not use a proxy pattern. The Minter contract, which is authorized to mint AERO, is also non-upgradeable.", - "evidence": [ - { - "urls": [ - { - "name": "AERO Token", - "url": "https://basescan.org/address/0x940181a94A35A4569E4529A3CDfB74e38FD98631#code", - "type": "explorer" - }, - { - "name": "Minter", - "url": "https://basescan.org/address/0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "AERO minting is fully programmatic and epoch-based (1 epoch = 1 week), enforced by the non-upgradeable Minter contract.", - "evidence": [ - { - "name": "Minter Contract", - "summary": "The non-upgradeable Minter contract enforces programmatic emission rules.", - "urls": [ - { - "name": "Minter", - "url": "https://basescan.org/address/0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5#code", - "type": "explorer" - } - ] - }, - { - "name": "Emission Rate", - "summary": "Can be increased/decreased by governance but only in fixed, small, weekly increments of +/-0.01%.", - "urls": [ - { - "name": "Minter Emission Increase (Epochs 1-14)", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L182", - "type": "github" - }, - { - "name": "Minter Emission Decay", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L184", - "type": "github" - } - ] - }, - { - "name": "Growth Emissions", - "summary": "Mints growth emissions for veAERO holders using the formula: weeklyEmissions * (1 - veAERO.totalSupply / AERO.totalSupply)^2 * 0.5. This creates a counter-cyclical incentive where veAERO rewards decrease as more AERO is locked.", - "urls": [ - { - "name": "Growth Emissions Formula", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L135", - "type": "github" - } - ] - }, - { - "name": "Team Emission Rate", - "summary": "Currently set to 0% of total weekly emissions.", - "urls": [ - { - "name": "Team Emission Rate", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L192", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "warning", - "notes": "The feeManager role exists which is controlled by a Safe multisig, not veAERO holders. This role can modify fee parameters across both pool types.", - "evidence": [ - { - "name": "Fee Manager Role", - "summary": "The feeManager role is controlled by a Safe multisig and can modify fee parameters.", - "urls": [ - { - "name": "Safe (Fee Manager)", - "url": "https://basescan.org/address/0xE6A41fE61E7a1996B59d508661e3f524d6A32075#code", - "type": "explorer" - } - ] - }, - { - "name": "Non-SlipStream Fees", - "summary": "Fees are set on the PoolFactory which can be changed by the feeManager.", - "urls": [ - { - "name": "PoolFactory: FeeManager", - "url": "https://basescan.org/address/0x420DD381b31aEf6683db6B902084cB0FFECe40Da#code", - "type": "explorer" - } - ] - }, - { - "name": "SlipStream Fees", - "summary": "For CLPoolFactory, fees are set inside the SwapFeeModule which allows feeManager to change fees.", - "urls": [ - { - "name": "SlipStream: CLFactory (uses SwapFeeModule for fees)", - "url": "https://basescan.org/address/0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A#code", - "type": "explorer" - }, - { - "name": "SwapFeeModule: setCustomFee", - "url": "https://basescan.org/address/0xF4171B0953b52Fa55462E4d76ecA1845Db69af00#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "The AERO token has no blacklist, pause, or seizure mechanisms.", - "evidence": [ - { - "name": "Team Rate", - "summary": "The team rate (share of weekly emissions allocated to the team) can be modified by a Safe controlled by the Aerodrome team.", - "urls": [ - { - "name": "Safe (Team)", - "url": "https://basescan.org/address/0xBDE0c70BdC242577c52dFAD53389F82fd149EA5a#code", - "type": "explorer" - }, - { - "name": "Team Rate Change", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L129", - "type": "github" - } - ] - }, - { - "name": "Emission Rate", - "summary": "Changes to the weekly emission rate (+/-0.01% or unchanged) are controlled by Governor (a multisig), soon to be handed over to governance contracts controlled by veAERO holders.", - "urls": [ - { - "name": "Governor (Safe)", - "url": "https://basescan.org/address/0xE6A41fE61E7a1996B59d508661e3f524d6A32075", - "type": "explorer" - }, - { - "name": "Emission Rate Change", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L145", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "veAERO holders receive growth emissions and trading fees from staked LP positions. There is no protocol treasury - all trading fees are distributed. Offchain brand controlled by Aerodrome Foundation.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "veAERO holders receive rewards from growth emissions and trading fees from staked LP positions.", - "evidence": [ - { - "name": "Growth Emissions", - "summary": "veAERO holders receive rewards from growth emissions transferred to RewardDistributor, claimable in proportion to ve balance.", - "urls": [ - { - "name": "Growth Transfer to RewardDistributor", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Minter.sol#L201", - "type": "github" - }, - { - "name": "RewardsDistributor Claim", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/RewardsDistributor.sol#L126", - "type": "github" - } - ] - }, - { - "name": "Non-Slipstream Trading Fees", - "summary": "For normal pools, swap fees from LPs who have staked their LP tokens in gauges go to veAERO holders.", - "urls": [ - { - "name": "Gauge Deposit Logic", - "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/gauges/Gauge.sol#L155", - "type": "github" - }, - { - "name": "Swap Fee Accrued", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Pool.sol#L378", - "type": "github" - }, - { - "name": "Swap Fee Claimed", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Pool.sol#L143", - "type": "github" - }, - { - "name": "Voter calling Gauge to claim fees", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/Voter.sol#L492", - "type": "github" - }, - { - "name": "Swap Fee Claimed by Gauge on PoolFees", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/gauges/Gauge.sol#L82", - "type": "github" - } - ] - }, - { - "name": "Slipstream Trading Fees", - "summary": "For Slipstream (CL) pools, swap fees from staked LP positions go to veAERO holders.", - "urls": [ - { - "name": "Fees Split", - "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/core/CLPool.sol#L844", - "type": "github" - }, - { - "name": "Gauge Fees calculated separately", - "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/core/CLPool.sol#L844C37-L844C46", - "type": "github" - }, - { - "name": "Non Staked LP Fees collected", - "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/core/CLPool.sol#L483", - "type": "github" - }, - { - "name": "Gauge Fees to feesVotingReward", - "url": "https://github.com/aerodrome-finance/slipstream/blob/7844368af8f83459b5056ff5f3334ff041232382/contracts/gauge/CLGauge.sol#L340", - "type": "github" - }, - { - "name": "veAERO voters claim fees", - "url": "https://github.com/aerodrome-finance/contracts/blob/1ba30815bba620f7e9faa34769ffd00c214c9b82/contracts/rewards/Reward.sol#L225", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "positive", - "notes": "There is no protocol treasury. All trading fees from swapping pools are distributed to veAERO holders and LPs.", - "evidence": [] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "veAERO holders control:\n\n1. The AERO rewards they receive by locking AERO tokens in VotingEscrow\n\n2. The AERO emissions going into gauges by voting for them in Voter\n\n**Note:** Fee parameters are controlled by the feeManager (a Safe multisig), not by veAERO holders directly.", - "evidence": [ - { - "name": "Locking Control", - "summary": "veAERO holders control their rewards by locking AERO in VotingEscrow.", - "urls": [ - { - "name": "VotingEscrow create_lock", - "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/VotingEscrow.sol#L555", - "type": "github" - } - ] - }, - { - "name": "Gauge Voting", - "summary": "veAERO holders direct emissions by voting for gauges in the Voter contract.", - "urls": [ - { - "name": "Voter vote function", - "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/Voter.sol#L249", - "type": "github" - }, - { - "name": "Gauge getReward", - "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/gauges/Gauge.sol#L179", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "Aragon has not verified additional offchain value accrual flows to the AERO token", - "evidence": [] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "AERO token source is publicly available on GitHub and verified on Basescan. Aerodrome protocol contracts are open source and verified.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The AERO token contract source code (Aero.sol) is publicly available on GitHub and verified on Basescan.", - "evidence": [ - { - "urls": [ - { - "name": "AERO Token (Basescan)", - "url": "https://basescan.org/address/0x940181a94A35A4569E4529A3CDfB74e38FD98631#code", - "type": "explorer" - }, - { - "name": "Aero.sol Source (GitHub)", - "url": "https://github.com/aerodrome-finance/contracts/blob/main/contracts/Aero.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "Aerodrome protocol contracts (including Slipstream) are open source on GitHub.", - "evidence": [ - { - "urls": [ - { - "name": "Aerodrome Contracts (GitHub)", - "url": "https://github.com/aerodrome-finance/contracts", - "type": "github" - }, - { - "name": "Aerodrome Slipstream (GitHub)", - "url": "https://github.com/aerodrome-finance/slipstream", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "Distribution of AERO and veAERO voting power outside of the core team has not been independently verified.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "unevaluated", - "notes": "Distribution of AERO and veAERO voting power outside of the core team has not been independently verified.", - "evidence": [] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "positive", - "notes": "Future AERO unlocks are determined by the supply schedule and the veAERO system detailed above. Aragon is not aware of any significant vesting cliffs.", - "evidence": [] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "US AERODROME trademark is held by Perpetual Cyclist Services LLC (Delaware). Primary interface terms identify the Aerodrome Foundation as the contracting party.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "A US AERODROME trademark filing lists Perpetual Cyclist Services LLC (Delaware) as the applicant/owner.", - "evidence": [ - { - "urls": [ - { - "name": "USPTO Report", - "url": "https://uspto.report/TM/99083103", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "The primary interface terms identify the Aerodrome Foundation as the contracting party.", - "evidence": [ - { - "urls": [ - { - "name": "Aerodrome Legal", - "url": "https://aero.drome.eth.link/legal", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "unevaluated", - "notes": "Aragon has not yet verified all the licensing criteria associated with the AERO token or the corresponding protocol and assets", - "evidence": [] - } - ] - } - ], - "crv": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "veCRV holders control all protocol operations through Aragon v1's Voting Contract and Agent. The CRV token is immutable with programmatic inflation and no censorship capabilities. Core protocol and pool contracts are immutable.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "Governance is enforced through Aragon v1's Voting Contract, which authorizes execution via the Agent contract. The Agent contract is set as the owner for all protocol-gated functions, ensuring veCRV holders control all operations.", - "evidence": [ - { - "urls": [ - { - "name": "Aragon Voting Contract", - "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356", - "type": "explorer" - }, - { - "name": "Agent Contract", - "url": "https://etherscan.io/address/0x40907540d8a6C65c637785e8f8B742ae6b0b9968", - "type": "explorer" - }, - { - "name": "CRV Token: Admin is set to Agent", - "url": "https://etherscan.io/token/0xd533a949740bb3306d119cc777fa900ba034cd52#readContract", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "All protocol roles are controlled by the Agent contract, ensuring veCRV holders maintain control over gauges, fees, and protocol expansion.", - "evidence": [ - { - "name": "Gauge Addition", - "summary": "Only the Agent can add new gauges via add_gauge on GaugeController.", - "urls": [ - { - "name": "GaugeController: admin is Agent", - "url": "https://etherscan.io/address/0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB#readContract", - "type": "explorer" - } - ] - }, - { - "name": "Pool Factory Ownership", - "summary": "New pools are deployed by Factory contracts controlled by OwnerProxy. Agent is responsible for adding new pools, giving veCRV holders control over protocol expansion.", - "urls": [ - { - "name": "Curve Pool Factory: admin is OwnerProxy", - "url": "https://etherscan.io/address/0xB9fc157394Af804a3578134A6585C0dC9cc990d4#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "Core protocol and pool contracts are immutable. Governance contracts (Agent, Voting) are upgradeable only by veCRV holders.", - "evidence": [ - { - "name": "Core Contracts", - "summary": "Core protocol and pool contracts are immutable. The CRV Token and GaugeController have admin set to Agent but cannot be upgraded.", - "urls": [ - { - "name": "CRV Token (Admin: Agent)", - "url": "https://etherscan.io/address/0xd533a949740bb3306d119cc777fa900ba034cd52", - "type": "explorer" - }, - { - "name": "GaugeController (Admin: Agent)", - "url": "https://etherscan.io/address/0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB#readContract", - "type": "explorer" - } - ] - }, - { - "name": "Agent Contract", - "summary": "Can be upgraded by calling setApp(Agent, ...) on the Kernel contract, which is only allowed by the Agent itself. Only veCRV holders can upgrade the Agent contract through governance voting.", - "urls": [ - { - "name": "Agent", - "url": "https://etherscan.io/address/0x40907540d8a6C65c637785e8f8B742ae6b0b9968#code", - "type": "explorer" - } - ] - }, - { - "name": "Voting Contract", - "summary": "Can be upgraded by calling setApp(Voting, ...) on the Kernel contract, which is only allowed by the Agent itself. Only veCRV holders can upgrade the Voting contract through governance voting.", - "urls": [ - { - "name": "Voting", - "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "The CRV token contract is immutable with no proxy patterns or upgrade mechanisms.", - "evidence": [ - { - "urls": [ - { - "name": "ERC20CRV.vy Source", - "url": "https://etherscan.io/token/0xd533a949740bb3306d119cc777fa900ba034cd52#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "CRV has a programmatic inflation schedule with yearly epochs and decreasing emission rate. All minting flows through the Minter contract based on gauge participation.", - "evidence": [ - { - "name": "Supply Schedule", - "summary": "CRV inflation is released in yearly epochs: each year allows a fixed maximum amount to be minted linearly over time, and at the end of the year the minting rate is reduced by a factor of 2^(1/4). Any unminted supply from a year is permanently lost.", - "urls": [ - { - "name": "CRV Emission Schedule (Docs)", - "url": "https://docs.curve.finance/user/curve-tokens/crv#emission-schedule", - "type": "docs" - }, - { - "name": "ERC20CRV.vy Yearly Epoch Logic", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/ERC20CRV.vy#L115C40-L115C59", - "type": "github" - } - ] - }, - { - "name": "Minting Access", - "summary": "Mint can only occur through the Minter contract which validates gauge participation. Amount minted depends on veCRV balance and provided LP tokens in a gauge.", - "urls": [ - { - "name": "ERC20CRV.vy Mint Access", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/ERC20CRV.vy#L333", - "type": "github" - }, - { - "name": "Minter.vy Gauge Validation", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/Minter.vy#L46", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "positive", - "notes": "Core protocol functions are either permissionless or gated by Agent contract (hence veCRV holders). For gate control related to fees/revenues, see accrual section.", - "evidence": [ - { - "name": "Add/Kill Gauge", - "summary": "To add a new gauge in GaugeController, veCRV holders must vote in majority. The admin on GaugeController is set to the Aragon Agent Contract, which sits as the final step in the governance process. Governance starts with the Aragon Voting contract that uses veCRV token for voting. veCRV holders can also kill a gauge, permanently setting its rewards to 0.", - "urls": [ - { - "name": "Gauge Kill Implementation", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/gauges/LiquidityGaugeV5.vy#L731", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "The CRV token has no transfer restrictions, blacklist functionality, or pausable transfers. There's Admin set (Agent) on the CRV token, but it can only update token's name and symbol.", - "evidence": [ - { - "urls": [ - { - "name": "ERC20CRV.vy Standard Transfer", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/ERC20CRV.vy#L272", - "type": "github" - }, - { - "name": "ERC20CRV.vy Metadata Logic", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/ERC20CRV.vy#L365", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "veCRV holders receive 50% of all trading fees distributed as crvUSD rewards. Value flows are programmatic through the gauge system. Offchain structure shows Swiss Stake AG controls the interface and trademark.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "veCRV holders receive 50% of all trading fees distributed as crvUSD rewards, plus boosted CRV emissions for liquidity provision. They also receive 80% of the accrued interest from all crvUSD markets.", - "evidence": [ - { - "name": "CRV Emissions Rewards", - "summary": "Liquidity Providers deposit LP tokens into Gauge Contracts. Once the gauge receives CRV emissions, LPs can claim proportional rewards. LPs can boost rewards up to 2.5x by locking CRV for veCRV.", - "urls": [ - { - "name": "working_balance reward calculation", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/fa127b1cb7bf83e4f3d605f7244b7b4ed5ebe053/contracts/gauges/LiquidityGaugeV5.vy#L210", - "type": "github" - } - ] - }, - { - "name": "Pool Rewards in crvUSD", - "summary": "Pools have swap fees with an admin portion collected by StableSwapProxy. Fees flow through burner contracts to FeeCollector, which converts all tokens to crvUSD via CowSwapBurner and sends to FeeDistributor for veCRV holders to claim.", - "urls": [ - { - "name": "FeeDistributor", - "url": "https://etherscan.io/address/0xD16d5eC345Dd86Fb63C6a9C43c517210F1027914", - "type": "explorer" - } - ] - }, - { - "name": "Borrow Rewards in crvUSD", - "summary": "Borrowing interest (paid in crvUSD) from controllers are sent to FeeSplitter. FeeCollector receives proportional crvUSD and sends to FeeDistributor for veCRV holders.", - "urls": [ - { - "name": "FeeSplitter", - "url": "https://etherscan.io/address/0x2dfd89449faff8a532790667bab21cf733c064f2", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "positive", - "notes": "50% of all trading fees are distributed to veCRV holders through crvUSD rewards, while the remaining 50% goes to the respective liquidity providers of the pools. Only veCRV holders can change this behaviour, hence Curve has no separate treasury. All revenue to the people.", - "evidence": [ - { - "urls": [ - { - "name": "veCRV Revenue Share (Docs)", - "url": "https://docs.curve.finance/user/vecrv/revenue", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "Gauge weights are determined programmatically by veCRV votes, not discretionary decisions.", - "evidence": [ - { - "name": "Pool Fee Control", - "summary": "Pool fee changes are executed via commit_new_fee, which can only be called by the pool owner (StableSwapProxy) which in turn is restricted to parameter_admin (Agent contract).", - "urls": [ - { - "name": "DAI/USDC/USDT Pool: commit_new_fee", - "url": "https://etherscan.io/address/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7#code", - "type": "explorer" - } - ] - }, - { - "name": "Burner Attachment", - "summary": "set_burner on StableSwapProxy is gated by ownership_admin (Agent contract).", - "urls": [ - { - "name": "StableSwapProxy: set_burner", - "url": "https://etherscan.io/address/0xeCb456EA5365865EbAb8a2661B0c503410e9B347#code", - "type": "explorer" - } - ] - }, - { - "name": "Burn Execution", - "summary": "The burn function invokes each coin's attached burner contract. Can be disabled by either the Agent contract or the emergency Safe multisig.", - "urls": [ - { - "name": "StableSwapProxy: burn", - "url": "https://etherscan.io/address/0xeCb456EA5365865EbAb8a2661B0c503410e9B347#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "Aragon has not verified additional offchain value accrual flows to the CRV token", - "evidence": [] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "CRV token source (Vyper) is publicly available on GitHub and verified on Etherscan. Curve DAO contracts are open source and verified.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The CRV token contract source code (ERC20CRV.vy) is publicly available on GitHub and verified on Etherscan.", - "evidence": [ - { - "urls": [ - { - "name": "CRV Token (Etherscan)", - "url": "https://etherscan.io/token/0xd533a949740bb3306d119cc777fa900ba034cd52#code", - "type": "explorer" - }, - { - "name": "ERC20CRV.vy Source (GitHub)", - "url": "https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/ERC20CRV.vy", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "Curve DAO protocol contracts are open source on GitHub.", - "evidence": [ - { - "urls": [ - { - "name": "Curve DAO Contracts (GitHub)", - "url": "https://github.com/curvefi/curve-dao-contracts", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "Aragon has not verified if the majority of veCRV voting power lies with a single party.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "unevaluated", - "notes": "Aragon has not yet verified if the majority of veCRV voting power lies with a single party", - "evidence": [] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "positive", - "notes": "Future CRV unlocks are determined by the programmatic supply schedule and veCRV system. Emissions decrease yearly by 2^(1/4). Aragon is not aware of any significant vesting cliffs.", - "evidence": [ - { - "urls": [ - { - "name": "CRV Emission Schedule (Docs)", - "url": "https://docs.curve.finance/user/curve-tokens/crv#emission-schedule", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Swiss CRV trademark is held by Swiss Stake AG. Curve interface terms identify Swiss Stake AG (Zug, Switzerland) as the contracting party.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "The Swiss CRV trademark registration lists Swiss Stake AG as the owner.", - "evidence": [ - { - "urls": [ - { - "name": "Swiss Trademark Registry", - "url": "https://www.swissreg.ch/database-client/register/detail?lang=de&no=16098%2F2020&type=trademark", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "The Curve interface terms identify Swiss Stake AG (Zug, Switzerland) as the contracting party.", - "evidence": [ - { - "urls": [ - { - "name": "Curve Legal", - "url": "https://www.curve.finance/dex/ethereum/legal", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "unevaluated", - "notes": "Aragon has not yet verified all the licensing criteria associated with the CRV token or the corresponding protocol and assets", - "evidence": [] - } - ] - } - ], - "ena": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "ENA governance is advisory only. Tokenholders vote via Snapshot, but the Dev Multisig executes all decisions. There is no onchain governance workflow, no timelock, and ENA holders cannot elect or remove multisig signers. The ENA token itself is non-upgradeable, but sENA and rsENA are upgradeable proxies controlled by separate multisigs. Staking contracts include blacklist capabilities with seizure powers.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "warning", - "notes": "ENA governance is **offchain Snapshot signaling only**. Votes do not trigger onchain transactions. The Dev Multisig decides whether to execute proposals, making tokenholder votes advisory rather than binding. There is no timelock on multisig actions.", - "evidence": [ - { - "name": "Snapshot Governance", - "summary": "ENA holders vote via Snapshot at ethenagovernance.eth. All passed votes require manual multisig execution. Per docs: \"fully on-chain governance is not a practical or viable option at present.\"", - "urls": [ - { - "name": "Snapshot Space", - "url": "https://snapshot.org/#/ethenagovernance.eth", - "type": "docs" - }, - { - "name": "Governance Documentation", - "url": "https://docs.ethena.fi/solution-overview/governance", - "type": "docs" - } - ] - }, - { - "name": "Multisig Execution", - "summary": "The Dev Multisig owns all core contracts. Verified onchain: `getThreshold()` returns 5, `getOwners()` returns 11 addresses. Documentation claims 4/8, but onchain reality is 5/11.", - "urls": [ - { - "name": "Dev Multisig", - "url": "https://etherscan.io/address/0x3b0aaf6e6fcd4a7ceef8c92c32dfea9e64dc1862", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "warning", - "notes": "All privileged roles are controlled by Ethena Labs multisigs or EOAs, **NOT** by ENA tokenholders. The Risk Committee is elected via Snapshot but has no onchain authority. No mechanism exists for ENA holders to remove or replace multisig signers.", - "evidence": [ - { - "name": "Multisig Control Matrix", - "summary": "**Dev Multisig:** All contract ownership, upgrades, minting, parameter changes. Signers NOT elected by ENA holders.\n\n**Hot Swap:** Protocol revenue flow, USDe conversion. Signers NOT elected.\n\n**sUSDe Payout:** Staker reward distribution. Signers NOT elected.\n\n**Trading Operations:** Onchain operational activities. Signers NOT elected.\n\n**Reserve Fund:** Emergency reserve deployment. Signers NOT elected.", - "urls": [ - { - "name": "Multisig Matrix Documentation", - "url": "https://docs.ethena.fi/solution-design/key-trust-assumptions/matrix-of-multisig-and-timelocks", - "type": "docs" - }, - { - "name": "Dev Multisig", - "url": "https://etherscan.io/address/0x3b0aaf6e6fcd4a7ceef8c92c32dfea9e64dc1862", - "type": "explorer" - }, - { - "name": "Hot Swap Multisig", - "url": "https://etherscan.io/address/0x4423198f26764a8ce9ac8f1683c476854c885d9d", - "type": "explorer" - }, - { - "name": "sUSDe Payout Multisig", - "url": "https://etherscan.io/address/0x71e4f98e8f20c88112489de3dded4489802a3a87", - "type": "explorer" - }, - { - "name": "Trading Operations Multisig", - "url": "https://etherscan.io/address/0x0a0b96A730ED5CDa84bcB63c1Ee2edCb6B7764d6", - "type": "explorer" - }, - { - "name": "Reserve Fund Multisig", - "url": "https://etherscan.io/address/0x2b5ab59163a6e93b4486f6055d33ca4a115dd4d5", - "type": "explorer" - } - ] - }, - { - "name": "EOA Control Points", - "summary": "**StakingRewardsDistributor Operator** (EOA): Can trigger `transferInRewards()` to distribute USDe to sUSDe stakers.\n\n**Minters** (20 EOAs): Can mint USDe via EthenaMinting.\n\n**Redeemers** (20 EOAs): Can redeem USDe.\n\n**Gatekeepers** (3+ EOAs): Can disable USDe mint/redeem globally.", - "urls": [ - { - "name": "StakingRewardsDistributor Operator (EOA)", - "url": "https://etherscan.io/address/0xe3880B792F6F0f8795CbAACd92E7Ca78F5d3646e", - "type": "explorer" - }, - { - "name": "Multisig Matrix (Minter/Redeemer/Gatekeeper docs)", - "url": "https://docs.ethena.fi/solution-design/key-trust-assumptions/matrix-of-multisig-and-timelocks", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "warning", - "notes": "**sENA is upgradeable** via proxy controlled by Dev Multisig. **rsENA is also upgradeable** but controlled by a different multisig. Neither upgrade path involves tokenholder approval. ENA, USDe, sUSDe, and EthenaMinting are non-upgradeable.", - "evidence": [ - { - "name": "Non-Upgradeable Contracts", - "summary": "ENA, USDe, sUSDe, and EthenaMinting V2 are not proxy contracts.", - "urls": [ - { - "name": "ENA Token", - "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", - "type": "explorer" - }, - { - "name": "USDe Token", - "url": "https://etherscan.io/address/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code", - "type": "explorer" - }, - { - "name": "sUSDe Token", - "url": "https://etherscan.io/address/0x9d39a5de30e57443bff2a8307a4256c8797a3497#code", - "type": "explorer" - }, - { - "name": "EthenaMinting V2", - "url": "https://etherscan.io/address/0xe3490297a08d6fC8Da46Edb7B6142E4F461b62D3#code", - "type": "explorer" - } - ] - }, - { - "name": "Upgradeable Contracts (sENA and rsENA)", - "summary": "**sENA:** Uses EIP-1967 proxy. Dev Multisig can upgrade without tokenholder approval. No timelock.\n\n**rsENA:** Uses EIP-1967 proxy with a different upgrade controller. Controlled by a separate multisig with signers not publicly identified. No timelock.", - "urls": [ - { - "name": "sENA Proxy", - "url": "https://etherscan.io/address/0x8bE3460A480c80728a8C4D7a5D5303c85ba7B3b9#code", - "type": "explorer" - }, - { - "name": "sENA Implementation", - "url": "https://etherscan.io/address/0x7fd57b46ae1a7b14f6940508381877ee03e1018b#code", - "type": "explorer" - }, - { - "name": "sENA ProxyAdmin", - "url": "https://etherscan.io/address/0xf849d7792ff9b30a57656ee10a2776bcb49f4fe4#code", - "type": "explorer" - }, - { - "name": "rsENA Proxy", - "url": "https://etherscan.io/address/0xc65433845ecd16688eda196497fa9130d6c47bd8#code", - "type": "explorer" - }, - { - "name": "rsENA Implementation", - "url": "https://etherscan.io/address/0x09bba67c316e59840699124a8dc0bbda6a2a9d59#code", - "type": "explorer" - }, - { - "name": "rsENA ProxyAdmin", - "url": "https://etherscan.io/address/0xa59b36aca119a30c527eddaa386eb130bcf1939f", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "warning", - "notes": "The ENA token itself is **NOT upgradeable**. It uses Ownable2Step, not a proxy pattern. Token behavior is immutable. However, the owner (Dev Multisig) retains mint authority subject to rate limits.", - "evidence": [ - { - "name": "ENA Non-Upgradeability Verification", - "summary": "The ENA token inherits Ownable2Step, ERC20Burnable, ERC20Permit. No upgrade mechanism exists in the contract.", - "urls": [ - { - "name": "ENA.sol Source", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol", - "type": "github" - }, - { - "name": "ENA Token (Etherscan)", - "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "warning", - "notes": "ENA has rate-limited but discretionary minting controlled by the Dev Multisig. Maximum 10% of total supply per mint, with 365-day cooldown between mints. No tokenholder approval required. Total supply is 15 billion ENA.", - "evidence": [ - { - "name": "Mint Function", - "summary": "The `mint()` function allows the owner to create new tokens subject to two constraints:\n\n**MAX_INFLATION = 10** (10% of total supply per mint)\n\n**MINT_WAIT_PERIOD = 365 days** (minimum time between mints)\n\nOwner (Dev Multisig) can invoke without tokenholder approval.", - "urls": [ - { - "name": "ENA.sol `mint()` function (lines 42-49)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol#L42-L49", - "type": "github" - }, - { - "name": "ENA Token totalSupply", - "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#readContract", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "warning", - "notes": "Gatekeepers can disable USDe minting/redemption globally. Only the Owner can re-enable. The StakingRewardsDistributor operator (a single EOA) controls when rewards are distributed to sUSDe stakers.", - "evidence": [ - { - "name": "Privileged Roles (per Multisig Matrix)", - "summary": "**Owner (EthenaMinting):** Can set max mint/redeem limits for USDe, add/remove collateral assets and custodians.\n\n**Admin (EthenaMinting):** Can grant/revoke Minter, Redeemer, Gatekeeper roles.\n\n**Gatekeeper:** Can call `disableMintRedeem()` to halt USDe operations globally.\n\n**Operator (StakingRewardsDistributor):** Single EOA that controls `transferInRewards()` to distribute USDe to sUSDe stakers.", - "urls": [ - { - "name": "Multisig Matrix Documentation", - "url": "https://docs.ethena.fi/solution-design/key-trust-assumptions/matrix-of-multisig-and-timelocks", - "type": "docs" - }, - { - "name": "EthenaMinting V2", - "url": "https://etherscan.io/address/0xe3490297a08d6fC8Da46Edb7B6142E4F461b62D3#code", - "type": "explorer" - } - ] - }, - { - "name": "Gatekeeper Powers", - "summary": "Gatekeepers can halt but **only the Owner can re-enable**. This creates asymmetric power.", - "urls": [ - { - "name": "`disableMintRedeem()` (line 278)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/EthenaMinting.sol#L278", - "type": "github" - }, - { - "name": "`removeMinterRole()` (line 339)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/EthenaMinting.sol#L339", - "type": "github" - }, - { - "name": "`removeRedeemerRole()` (line 345)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/EthenaMinting.sol#L345", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "warning", - "notes": "The ENA base token has no blacklist capability. However, sENA and sUSDe staking contracts include BLACKLIST_MANAGER_ROLE with freeze and seizure powers. FULL_RESTRICTED addresses cannot transfer tokens, and `redistributeLockedAmount()` allows admin to seize frozen assets.", - "evidence": [ - { - "name": "sENA/sUSDe Blacklist Capabilities", - "summary": "StakedUSDe.sol defines three restriction roles:\n\n**SOFT_RESTRICTED_STAKER_ROLE:** Cannot stake/unstake\n\n**FULL_RESTRICTED_STAKER_ROLE:** Cannot transfer at all (frozen)\n\n**BLACKLIST_MANAGER_ROLE:** Can assign restrictions\n\nThe `redistributeLockedAmount()` function allows admin to seize and redistribute frozen assets.", - "urls": [ - { - "name": "StakedUSDe.sol blacklist roles (lines 26-32)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/StakedUSDe.sol#L26-L32", - "type": "github" - }, - { - "name": "`redistributeLockedAmount()` (lines 106-127)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/StakedUSDe.sol#L106-L127", - "type": "github" - }, - { - "name": "sUSDe Token", - "url": "https://etherscan.io/address/0x9d39a5de30e57443bff2a8307a4256c8797a3497#code", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "No active programmatic value accrual to ENA holders. **sUSDe holders receive USDe yield** from protocol operations; **sENA holders do NOT receive this yield**, only ecosystem airdrops. rsENA holders receive Symbiotic restaking rewards. Treasury flows through multisig-controlled wallets. All accrual mechanisms are controlled by multisigs or EOAs, not tokenholders.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "warning", - "notes": "**sUSDe holders receive USDe yield; sENA/ENA holders do NOT.** sENA holders receive only ecosystem airdrops from Ethena Network protocols. rsENA (~5.2M supply) earns Symbiotic restaking rewards via Mellow Finance. Fee switch (which would share protocol revenue with sENA) received positive forum signals but awaits Snapshot vote and execution.", - "evidence": [ - { - "name": "Fee Switch Status", - "summary": "Forum posts received positive signals in November 2024. USDe supply ~5.98B (the $6B threshold has **not** been met). Cumulative revenue $500M+ as of Sept 2025. Activation requires Risk Committee sign-off + Snapshot vote + multisig execution.", - "urls": [ - { - "name": "Fee Switch Parameters Proposal", - "url": "https://gov.ethenafoundation.com/t/ena-fee-switch-parameters/396", - "type": "docs" - }, - { - "name": "USDe Token (verify totalSupply)", - "url": "https://etherscan.io/address/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#readContract", - "type": "explorer" - }, - { - "name": "DefiLlama - Ethena Fees", - "url": "https://defillama.com/protocol/ethena", - "type": "docs" - } - ] - }, - { - "name": "Ethena Network Airdrops", - "summary": "Protocols in the Ethena Network commit portions of their token supply to sENA holders. Example: Ethereal committed 15% of tokens to sENA holders.", - "urls": [ - { - "name": "Ethena Network Documentation", - "url": "https://docs.ethena.fi/ethena-network", - "type": "docs" - } - ] - }, - { - "name": "rsENA (Restaked ENA via Symbiotic)", - "summary": "rsENA (~5.2M supply) provides economic security for USDe cross-chain transfers using LayerZero DVN messaging via Symbiotic partnership. rsENA holders receive rewards in **ENA and USDe** per docs. Available via **Mellow Finance vault**.", - "urls": [ - { - "name": "rsENA Contract", - "url": "https://etherscan.io/address/0xc65433845ecd16688eda196497fa9130d6c47bd8#readContract", - "type": "explorer" - }, - { - "name": "ENA Staking Documentation", - "url": "https://docs.ethena.fi/ena", - "type": "docs" - }, - { - "name": "Mellow Finance rsENA Vault", - "url": "https://app.mellow.finance/vaults/ethereum-rsena", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "warning", - "notes": "Protocol revenue flows through multisig-controlled wallets. **Treasury is NOT tokenholder-controlled.** Revenue flows: Hot Swap \u2192 sUSDe Payout \u2192 StakingRewardsDistributor \u2192 sUSDe stakers. ENA tokenholders do NOT control treasury flows.", - "evidence": [ - { - "name": "Revenue Flow", - "summary": "Protocol Operations (delta-neutral strategies)\n\u2192 **Hot Swap Multisig** (receives revenue, converts to USDe)\n\u2192 **sUSDe Payout Multisig**\n\u2192 **StakingRewardsDistributor**\n\u2192 sUSDe Stakers\n\nENA/sENA holders are NOT in this flow unless fee switch activates.", - "urls": [ - { - "name": "Key Addresses Documentation", - "url": "https://docs.ethena.fi/solution-design/key-addresses", - "type": "docs" - }, - { - "name": "Hot Swap Multisig (revenue receiver)", - "url": "https://etherscan.io/address/0x4423198f26764a8ce9ac8f1683c476854c885d9d", - "type": "explorer" - }, - { - "name": "sUSDe Payout Multisig", - "url": "https://etherscan.io/address/0x71e4f98e8f20c88112489de3dded4489802a3a87", - "type": "explorer" - }, - { - "name": "StakingRewardsDistributor", - "url": "https://etherscan.io/address/0xf2fa332bd83149c66b09b45670bce64746c6b439#code", - "type": "explorer" - } - ] - }, - { - "name": "Reserve Fund (Negative Funding Backup)", - "summary": "Separate from revenue treasury. Used only for negative funding emergencies. NOT tokenholder-controlled.", - "urls": [ - { - "name": "Reserve Fund Multisig", - "url": "https://etherscan.io/address/0x2b5ab59163a6e93b4486f6055d33ca4a115dd4d5", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "warning", - "notes": "All ENA value accrual mechanisms are controlled by multisigs or EOAs, **NOT** by ENA tokenholders. Fee switch requires discretionary multisig execution. Ecosystem airdrops are Foundation-negotiated. sENA/rsENA can be upgraded without tokenholder vote.", - "evidence": [ - { - "name": "ENA Value Accrual Controls", - "summary": "**Fee Switch Activation:** Dev Multisig + Risk Committee. Snapshot votes are advisory only.\n\n**Ethena Network Airdrops:** Ethena Foundation negotiates allocations.\n\n**sENA Upgrade:** Dev Multisig via ProxyAdmin. Unilateral, no timelock.\n\n**rsENA Upgrade:** Separate multisig. Unilateral, no timelock.\n\n**rsENA Restaking Rewards:** Symbiotic integration.\n\n**Upgrade Risk:** Contract upgrades could modify or eliminate value accrual mechanisms without tokenholder approval.", - "urls": [ - { - "name": "Fee Switch Parameters Proposal", - "url": "https://gov.ethenafoundation.com/t/ena-fee-switch-parameters/396", - "type": "docs" - }, - { - "name": "sENA ProxyAdmin", - "url": "https://etherscan.io/address/0xf849d7792ff9b30a57656ee10a2776bcb49f4fe4", - "type": "explorer" - }, - { - "name": "rsENA ProxyAdmin Owner (5-of-8 Multisig)", - "url": "https://etherscan.io/address/0x27a907d1f809e8c03d806dc31c8e0c545a3187fc", - "type": "explorer" - } - ] - }, - { - "name": "Tokenholder Cannot Force Activation", - "summary": "Even with majority sENA/ENA support, tokenholders cannot force fee switch activation or change airdrop terms. Snapshot votes signal preference but the Dev Multisig decides execution. No onchain mechanism exists for binding governance over value accrual.", - "urls": [ - { - "name": "Governance Documentation", - "url": "https://docs.ethena.fi/solution-overview/governance", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "warning", - "notes": "USDe yield comes from three sources: CEX funding rates (unverifiable), ETH staking (~3-4%), and Treasury/BUIDL (~4-5%). **This yield flows to sUSDe stakers only; ENA/sENA holders receive none of it.** Revenue is controlled by multisigs, not programmatic.", - "evidence": [ - { - "name": "USDe Yield Sources", - "summary": "**CEX Funding Rates:** Delta-neutral hedging (long spot, short perps). Variable 5-20%+ yield. Not verifiable onchain (CEX positions).\n\n**ETH Staking:** stETH/wBETH collateral earns validator rewards (~3-4%). Partially verifiable (collateral visible).\n\n**Treasury/BUIDL:** USDtb backed by BlackRock BUIDL fund (~4-5%). Partially verifiable (USDtb holdings).", - "urls": [ - { - "name": "Coin Metrics Analysis (USDe Yield Sources)", - "url": "https://coinmetrics.substack.com/p/state-of-the-network-issue-335", - "type": "docs" - } - ] - }, - { - "name": "Yield Distribution Flow", - "summary": "1. Yield generated offchain (CEX funding) and onchain (staking, treasury)\n2. Revenue settles through Copper ClearLoop custody (offchain)\n3. Hot Swap multisig receives and converts to USDe\n4. sUSDe Payout multisig transfers to StakingRewardsDistributor\n5. Operator EOA calls `transferInRewards()` to distribute to **sUSDe stakers only**\n\n**ENA/sENA holders do NOT receive USDe yield.**", - "urls": [ - { - "name": "`transferInRewards()` (lines 88-94)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/StakingRewardsDistributor.sol#L88-L94", - "type": "github" - }, - { - "name": "DefiLlama - Ethena Fees", - "url": "https://defillama.com/protocol/ethena", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "All core contracts are verified on Etherscan and open source on GitHub. Multiple security audits completed.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The ENA token contract is verified on Etherscan and matches the source code on GitHub. Solidity version 0.8.20, GPL-3.0 license.", - "evidence": [ - { - "urls": [ - { - "name": "ENA Token (Etherscan)", - "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", - "type": "explorer" - }, - { - "name": "ENA.sol Source (GitHub)", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "All core protocol contracts are verified on Etherscan. Most have open source GitHub repos. **sENA is verified on Etherscan but no public GitHub repo has been identified.** Multiple audits completed.", - "evidence": [ - { - "name": "Verified Contracts", - "urls": [ - { - "name": "ENA Token", - "url": "https://etherscan.io/address/0x57e114B691Db790C35207b2e685D4A43181e6061#code", - "type": "explorer" - }, - { - "name": "sENA Proxy", - "url": "https://etherscan.io/address/0x8bE3460A480c80728a8C4D7a5D5303c85ba7B3b9#code", - "type": "explorer" - }, - { - "name": "rsENA Proxy", - "url": "https://etherscan.io/address/0xc65433845ecd16688eda196497fa9130d6c47bd8#code", - "type": "explorer" - }, - { - "name": "USDe Token", - "url": "https://etherscan.io/address/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code", - "type": "explorer" - }, - { - "name": "sUSDe Token", - "url": "https://etherscan.io/address/0x9d39a5de30e57443bff2a8307a4256c8797a3497#code", - "type": "explorer" - }, - { - "name": "EthenaMinting V2", - "url": "https://etherscan.io/address/0xe3490297a08d6fC8Da46Edb7B6142E4F461b62D3#code", - "type": "explorer" - }, - { - "name": "StakingRewardsDistributor", - "url": "https://etherscan.io/address/0xf2fa332bd83149c66b09b45670bce64746c6b439#code", - "type": "explorer" - } - ] - }, - { - "name": "GitHub Repository", - "urls": [ - { - "name": "Ethena Public Assets (GitHub)", - "url": "https://github.com/ethena-labs/bbp-public-assets", - "type": "github" - } - ] - }, - { - "name": "Audits", - "urls": [ - { - "name": "Code4rena 2023 Audit", - "url": "https://github.com/code-423n4/2023-10-ethena", - "type": "github" - }, - { - "name": "Code4rena 2024 Audit", - "url": "https://github.com/code-423n4/2024-11-ethena-labs", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "70% insider allocation. Vesting unlocks continue through April 2028 for all categories. Circulating supply is approximately 55% of total.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "warning", - "notes": "Initial allocation heavily favors insiders: Core Contributors (30%), Investors (25%), Foundation (15%), Ecosystem Development (28%), Binance Launchpool (2%). The combined insider bloc (Contributors + Investors + Foundation) controls **70%** of total supply.", - "evidence": [ - { - "name": "Token Allocation", - "summary": "Total supply: 15 billion ENA.\nCirculating: ~8.2 billion (55%).\nLocked: ~6.8 billion (45%).\n\n**Insider bloc:** Contributors (30%) + Investors (25%) + Foundation (15%) = 70%", - "urls": [ - { - "name": "Tokenomist.ai - ENA", - "url": "https://tokenomist.ai/ethena", - "type": "docs" - }, - { - "name": "ENA Tokenomics Documentation", - "url": "https://docs.ethena.fi/ena/tokenomics", - "type": "docs" - } - ] - } - ] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "warning", - "notes": "Monthly unlocks continue for all vesting categories through April 2028. Contributors, Investors, and Ecosystem receive linear monthly unlocks. Foundation allocation has no disclosed vesting.", - "evidence": [ - { - "name": "Vesting Schedules", - "summary": "**Core Contributors** (30%): 1-year cliff (25%), then 3-year linear monthly. Full unlock ~April 2028.\n\n**Investors** (25%): 1-year cliff (25%), then 3-year linear monthly. Full unlock ~April 2028.\n\n**Ecosystem Development** (28%): Linear over 4 years. Full unlock ~April 2028.\n\n**Foundation** (15%): Discretionary, no vesting disclosed.\n\nNext unlock: March 2, 2026 (~40.6M ENA to Contributors).", - "urls": [ - { - "name": "ENA Tokenomics Documentation", - "url": "https://docs.ethena.fi/ena/tokenomics", - "type": "docs" - }, - { - "name": "Tokenomist.ai - ENA Vesting", - "url": "https://tokenomist.ai/ethena", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": [ - "Reference" - ], - "about": "Economically material assets and obligations often sit offchain, outside the custody and direct enforcement of a token, while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Trademarks and IP owned by Ethena (BVI) Limited, not controlled by ENA tokenholders. Primary interfaces operate under BVI law. Code is open source but copyright belongs to Ethena Labs.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Trademarks and brand assets are owned by **Ethena (BVI) Limited** (Registration number 2127704), a British Virgin Islands entity. Per Terms of Service: \"The Company's name, trademarks and logos... are trademarks of the Company or its affiliates.\" This entity is NOT controlled by ENA tokenholders.", - "evidence": [ - { - "urls": [ - { - "name": "Ethena Terms of Service", - "url": "https://docs.ethena.fi/resources/terms-of-service", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "The primary interface domain (ethena.fi) and Terms of Service identify **Ethena (BVI) Limited** as the contracting party, governed by British Virgin Islands law. ENA holders have no legal claim or control over the primary interface.", - "evidence": [ - { - "urls": [ - { - "name": "Ethena Terms of Service", - "url": "https://docs.ethena.fi/resources/terms-of-service", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Smart contract code is licensed under GPL-3.0, making it open source. However, per Terms of Service: \"the Company and/or its licensors own all right, title and interest in and to the Services.\" Copyright belongs to Ethena Labs. ENA holders do NOT control IP or licensing rights.", - "evidence": [ - { - "urls": [ - { - "name": "Ethena GitHub License", - "url": "https://github.com/ethena-labs/bbp-public-assets/blob/main/contracts/contracts/ENA.sol", - "type": "github" - }, - { - "name": "Ethena Terms of Service", - "url": "https://docs.ethena.fi/resources/terms-of-service", - "type": "docs" - } - ] - } - ] - } - ] - } - ], - "ldo": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "LDO holders exercise ultimate control through a multi-step governance flow with stETH holder veto protection via Dual Governance. All critical roles flow through governance-controlled contracts. The token has unbounded supply controlled by governance, with token behavior modifiable through controller upgrades.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "LDO holders exercise control through multiple governance paths with stETH holder veto protection via Dual Governance.", - "evidence": [ - { - "name": "Governance 1A", - "summary": "Voting (LDO holders) β†’ DualGovernance (stETH challenge window) β†’ EmergencyProtectedTimeLock (time delay) β†’ Executor β†’ Agent β†’ Protocol Contracts. LDO holders have ultimate control, constrained by stETH right to exit when disagreeing. This flow is used for protocol-related contracts.", - "urls": [ - { - "name": "Voting", - "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", - "type": "explorer" - }, - { - "name": "DualGovernance", - "url": "https://etherscan.io/address/0xC1db28B3301331277e307FDCfF8DE28242A4486E#code", - "type": "explorer" - }, - { - "name": "EmergencyProtectedTimeLock", - "url": "https://etherscan.io/address/0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316#code", - "type": "explorer" - }, - { - "name": "Executor", - "url": "https://etherscan.io/address/0x23E0B465633FF5178808F4A75186E2F2F9537021#code", - "type": "explorer" - }, - { - "name": "Agent", - "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - "type": "explorer" - } - ] - }, - { - "name": "Governance 1B", - "summary": "Voting (LDO holders) β†’ Protocol Contracts. This flow is used for DAO-related contracts.", - "urls": [ - { - "name": "Voting", - "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", - "type": "explorer" - } - ] - }, - { - "name": "Easy Track", - "summary": "An optimistic governance system where certain operations can be vetoed by LDO holders but are assumed to pass. Used for granting, treasury operations, and staking module management. Motions pass automatically unless β‰₯0.5% LDO objects within 72 hours. Permissionless execution post-timelock if unopposed; rejected motions escalate to full Aragon vote.", - "urls": [ - { - "name": "Easy Track Interface", - "url": "https://easytrack.lido.fi/", - "type": "docs" - }, - { - "name": "Easy Track Guide", - "url": "https://docs.lido.fi/guides/easy-track-guide", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "All critical roles flow through governance-controlled contracts, ensuring LDO holders maintain ultimate control over the protocol.", - "evidence": [ - { - "name": "Voting", - "summary": "CREATE_VOTES_ROLE allows proposing votes. All LDO holders can vote on proposals. The Voting contract controls the Agent via Governance 1A flow.", - "urls": [ - { - "name": "Voting", - "url": "https://etherscan.io/address/0xe478de485ad2fe566d49342cbd03e49ed7db3356#code", - "type": "explorer" - } - ] - }, - { - "name": "Agent", - "summary": "EXECUTE_ROLE is given to Executor. Executor is owned by EmergencyProtectedTimeLock, which is controlled by Governance 1A, which is controlled by Voting contract.", - "urls": [ - { - "name": "Agent", - "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - "type": "explorer" - } - ] - }, - { - "name": "TokenManager", - "summary": "MINT_ROLE is given to Voting contract. BURN_ROLE is given to no one, but its permission manager is Voting contract.", - "urls": [ - { - "name": "TokenManager (Aragon App V1 proxy)", - "url": "https://etherscan.io/address/0xf73a1260d222f447210581DDf212D915c09a3249", - "type": "explorer" - } - ] - }, - { - "name": "StakingRouter", - "summary": "STAKING_MODULE_MANAGE_ROLE is given to Agent contract. Since Agent is only callable by Governance 1, all operations are controlled by LDO holders.", - "urls": [ - { - "name": "StakingRouter", - "url": "https://etherscan.io/address/0xfddf38947afb03c621c71b06c9c70bce73f12999#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "Protocol contracts are upgradeable by LDO holders. Core governance contracts (DualGovernance, Executor, EmergencyProtectedTimeLock) are non-upgradeable.", - "evidence": [ - { - "name": "Upgradeable Contracts", - "summary": "StakingRouter: proxy_getAdmin is set to Agent.\n\nAgent: Uses Aragon v1 Proxy. Upgrading requires calling setApp(Agent, ...) on the Kernel, which requires APP_MANAGER_ROLE (currently given to Agent itself).", - "urls": [ - { - "name": "StakingRouter", - "url": "https://etherscan.io/address/0xfddf38947afb03c621c71b06c9c70bce73f12999#code", - "type": "explorer" - }, - { - "name": "Agent", - "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - "type": "explorer" - }, - { - "name": "Kernel", - "url": "https://etherscan.io/address/0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", - "type": "explorer" - } - ] - }, - { - "name": "Non-upgradeable Contracts", - "summary": "DualGovernance, Executor, and EmergencyProtectedTimeLock are immutable contracts that cannot be upgraded.", - "urls": [ - { - "name": "DualGovernance", - "url": "https://etherscan.io/address/0xC1db28B3301331277e307FDCfF8DE28242A4486E#code", - "type": "explorer" - }, - { - "name": "Executor", - "url": "https://etherscan.io/address/0x23E0B465633FF5178808F4A75186E2F2F9537021#code", - "type": "explorer" - }, - { - "name": "EmergencyProtectedTimeLock", - "url": "https://etherscan.io/address/0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "LDO token is immutable but **partially upgradeable** in practice due to its controller (TokenManager) being upgradeable via Aragon proxy.", - "evidence": [ - { - "name": "Token Contract", - "summary": "The LDO token contract is immutable with no proxy pattern. However, it has a controller address (TokenManager) that can call privileged functions (generateTokens, destroyTokens, enableTransfers, claimTokens). The token's doTransfer function includes a hook that calls the controller.", - "urls": [ - { - "name": "LDO Token", - "url": "https://etherscan.io/token/0x5a98fcbea516cf06857215779fd812ca3bef1b32#code", - "type": "explorer" - } - ] - }, - { - "name": "Controller Upgrade Path", - "summary": "TokenManager is upgradeable via Aragon proxy. Upgrading requires calling setApp(tokenManager, ...) on the Kernel, protected by APP_MANAGER_ROLE, which is assigned to the Agent contract (hence LDO holders).", - "urls": [ - { - "name": "TokenManager (Aragon App V1 proxy)", - "url": "https://etherscan.io/address/0xf73a1260d222f447210581DDf212D915c09a3249", - "type": "explorer" - }, - { - "name": "Kernel", - "url": "https://etherscan.io/address/0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "No supply cap exists, but all mints require LDO tokenholder approval through the Voting contract.", - "evidence": [ - { - "name": "Minting Path", - "summary": "The LDO token's controller (TokenManager) can mint unlimited tokens. However, minting requires MINT_ROLE on TokenManager, which is held by the Voting contract.\n\nMinting path: Voting (LDO holders) β†’ TokenManager β†’ Token.generateTokens()", - "urls": [ - { - "name": "MiniMeToken.generateTokens", - "url": "https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L385", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "positive", - "notes": "Deposits are operationally executed by Guardians (node operators + LDO dev team), but Guardians are fully accountable to LDO holders and cannot control protocol parameters.", - "evidence": [ - { - "name": "Guardian Role", - "summary": "User funds deposited into Lido are accumulated in the buffer and can only be deposited into a staking module by DepositSecurityModule, controlled by Guardians. Guardians use automated software for deposit operations.", - "urls": [ - { - "name": "DepositSecurityModule", - "url": "https://etherscan.io/address/0xfFA96D84dEF2EA035c7AB153D8B991128e3d72fD#code", - "type": "explorer" - }, - { - "name": "Lido.sol deposit logic", - "url": "https://github.com/lidofinance/core/blob/d5d92266b5bb305044c5dcf3e407463f776a4def/contracts/0.4.24/Lido.sol#L641", - "type": "github" - }, - { - "name": "Guardians use automated software", - "url": "https://docs.lido.fi/guides/deposit-security-manual/#tldr", - "type": "docs" - } - ] - }, - { - "name": "Guardian Accountability", - "summary": "Guardians are fully accountable to LDO holdersβ€”they can be rotated, replaced, or their mandate changed through onchain voting. Guardians do not control protocol parameters or economic risk (staking ratios, fee splits, module shares, or risk parameters).", - "urls": [ - { - "name": "Guardian Committee Membership", - "url": "https://docs.lido.fi/guides/deposit-security-manual#committee-membership", - "type": "docs" - }, - { - "name": "Guardians cannot control stake allocation", - "url": "https://github.com/lidofinance/core/blob/d5d92266b5bb305044c5dcf3e407463f776a4def/contracts/0.4.24/Lido.sol#L647", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "Controller has burn and transfer-disable capabilities, but BURN_ROLE is currently unassigned. Enabling burning requires LDO governance approval.", - "evidence": [ - { - "name": "Burn Capability", - "summary": "Controller can burn tokens from any address via destroyTokens. Currently, BURN_ROLE on TokenManager is given to no one, but permission manager is Voting contract. To enable burning, proposal must go through Governance 1B to call setPermission on ACL.", - "urls": [ - { - "name": "MiniMeToken.destroyTokens", - "url": "https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L401", - "type": "github" - }, - { - "name": "Aragon ACL", - "url": "https://etherscan.io/address/0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb", - "type": "explorer" - } - ] - }, - { - "name": "Transfer Control", - "summary": "Controller can enable/disable transfers globally via enableTransfers function.", - "urls": [ - { - "name": "MiniMeToken.enableTransfers", - "url": "https://github.com/aragon/minime/blob/1d5251fc88eee5024ff318d95bc9f4c5de130430/contracts/MiniMeToken.sol#L419", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "Protocol revenue flows to the LDO-controlled DAO treasury, and a newly approved manual buyback program (up to 10,000 stETH, held by the treasury) creates net buy pressure on LDO, though not a direct distribution. Treasury is controlled by LDO holders; offchain IP is held by DAO-controlled BORG foundations.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "**Protocol fee flow to treasury:**\n- Lido charges a 10% protocol fee on all ETH staking rewards, split onchain by the StakingRouter into a module fee (to node operators) and a treasury fee (to the LDO-controlled DAO Agent).\n- Calling `getStakingFeeAggregateDistribution()` on the StakingRouter currently returns an aggregated treasury fee of ~6.15% and module fees of ~3.85% (basePrecision = 100), meaning ~6.15% of all ETH staking rewards accrue to the DAO treasury on every oracle report.\n\n**Buyback program:**\n- Governance has authorized the Lido Growth Committee to buy back LDO using up to 10,000 stETH (from the accumulated fee described above), in 1,000 stETH batches via Easy Track motions (3-day objection window for LDO holders, 3% max slippage) while a favorable LDO/stETH ratio persists. Execution spans onchain (CoW, 1inch, Uniswap) and offchain venues (Binance, Bybit, OKX, Gate, Bitget).\n- This is independent of the anticipated automated NEST buybacks in Q2 2026. Value accrues to the LDO-controlled DAO treasury (buyback-and-hold), but is not directly distributed to LDO holders.", - "evidence": [ - { - "name": "Treasury Fee Flow", - "urls": [ - { - "name": "StakingRouter (read contract)", - "url": "https://etherscan.io/address/0xFdDf38947aFB03C621C71b06C9C70bce73f12999#readProxyContract", - "type": "explorer" - }, - { - "name": "StakingRouter.sol#L1051 (getStakingFeeAggregateDistribution)", - "url": "https://github.com/lidofinance/core/blob/master/contracts/0.8.9/StakingRouter.sol#L1051", - "type": "github" - } - ] - }, - { - "name": "Buyback Program", - "urls": [ - { - "name": "stETH/LDO Buyback Proposal", - "url": "https://research.lido.fi/t/utilizing-market-opportunities-steth-ldo-trade/11358", - "type": "docs" - }, - { - "name": "stETH/LDO Buyback Snapshot Vote", - "url": "https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x43be9ee8ce820d444f706e9dd763a223ebabf37be27931cc056888e6c2e48814", - "type": "vote" - }, - { - "name": "NEST", - "url": "https://research.lido.fi/t/nest-network-economic-support-tokenomics/10648", - "type": "docs" - }, - { - "name": "Liquid buybacks research", - "url": "https://research.lido.fi/t/liquid-buybacks-nest-execution-with-ldo-wsteth-liquidity/10894", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "positive", - "notes": "Treasury is the Agent contract, fully controlled by LDO holders. Treasury decisions are excluded from Governance 1 scope (stETH holders cannot challenge).", - "evidence": [ - { - "name": "Treasury Control", - "summary": "The Lido DAO Treasury is the Agent contract itself. The Treasury Management Committee proposes and enacts strategies via Governance 2 (Easy Track). All treasury decisions are controlled by LDO holders.", - "urls": [ - { - "name": "Agent (Treasury)", - "url": "https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - "type": "explorer" - } - ] - }, - { - "name": "Revenue Flow", - "summary": "LDO holders, via Governance 1A, control treasury revenue by approving staking modules and setting each module's treasury fee in the StakingRouter. This fee determines the portion of staking rewards routed to the Lido treasury.", - "urls": [ - { - "name": "StakingRouter", - "url": "https://etherscan.io/address/0xfddf38947afb03c621c71b06c9c70bce73f12999#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "To add a new staking module or update it with new fees, full governance flow required, hence controlled by LDO holders.", - "evidence": [ - { - "urls": [ - { - "name": "StakingRouter add/update", - "url": "https://github.com/lidofinance/core/blob/cca04b42123735714d8c60a73c2f7af949e989db/contracts/0.8.9/StakingRouter.sol#L227", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "Aragon has not verified additional offchain value accrual flows to the LDO token", - "evidence": [] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "LDO token source is publicly available and verified on Etherscan. Lido core protocol contracts are open source and verified.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The LDO token contract source code is publicly available on GitHub (MiniMeToken) and verified on Etherscan.", - "evidence": [ - { - "urls": [ - { - "name": "LDO Token (Etherscan)", - "url": "https://etherscan.io/token/0x5a98fcbea516cf06857215779fd812ca3bef1b32#code", - "type": "explorer" - }, - { - "name": "MiniMeToken Source (GitHub)", - "url": "https://github.com/aragon/minime", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "Lido core protocol contracts are open source on GitHub.", - "evidence": [ - { - "urls": [ - { - "name": "Lido Core Contracts (GitHub)", - "url": "https://github.com/lidofinance/core", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "Aragon has not yet verified the distribution of LDO holders outside of the core team.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "unevaluated", - "notes": "Aragon has not yet verified the distribution of LDO holders outside of the core team is greater than 50%", - "evidence": [] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "unevaluated", - "notes": "Aragon has not yet verified the supply schedule criteria for the LDO token", - "evidence": [] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "European trademark registrations for LIDO list Lido Labs Foundation as the owner. Lido Labs, Ecosystem, and Alliance BORG Foundations are DAO-controlled entities managing offchain IP and distribution.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "positive", - "notes": "European trademark registrations for LIDO list Lido Labs Foundation as the owner.", - "evidence": [ - { - "urls": [ - { - "name": "UK IPO Trademark Journal", - "url": "https://www.ipo.gov.uk/types/tm/t-os/t-tmj/tm-journals/2025-045/UK00004285645.html", - "type": "docs" - }, - { - "name": "Lido Logo", - "url": "https://euipo.europa.eu/eSearch/#details/trademarks/019182074", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "positive", - "notes": "Lido Labs BORG Foundation, Lido Ecosystem BORG Foundation, Lido Alliance BORG Foundation is a memberless DAO-adjacent foundation companies under which the Lido DAO has defined governance controls (including appointing/removing directors and overseeing BORG structures).", - "evidence": [ - { - "urls": [ - { - "name": "Lido Labs BORG Foundation", - "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xdf648307e68415e7b5cf96c6afbabd696c1731839f4b4a7cf5cb7efbc44ee9d6", - "type": "docs" - }, - { - "name": "Lido Ecosystem BORG Foundation", - "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0x7f72f12d72643c20cd0455c603d344050248e75ed1074c8391fae4c30f09ca15", - "type": "docs" - }, - { - "name": "Lido Alliance BORG Foundation", - "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xa478fa5518769096eda2b7403a1d4104ca47de3102e8a9abab8640ef1b50650c", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "positive", - "notes": "Lido Labs BORG Foundation, Lido Ecosystem BORG Foundation, Lido Alliance BORG Foundation is a memberless DAO-adjacent foundation companies under which the Lido DAO has defined governance controls (including appointing/removing directors and overseeing BORG structures).", - "evidence": [ - { - "urls": [ - { - "name": "Lido Labs BORG Foundation", - "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xdf648307e68415e7b5cf96c6afbabd696c1731839f4b4a7cf5cb7efbc44ee9d6", - "type": "docs" - }, - { - "name": "Lido Ecosystem BORG Foundation", - "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0x7f72f12d72643c20cd0455c603d344050248e75ed1074c8391fae4c30f09ca15", - "type": "docs" - }, - { - "name": "Lido Alliance BORG Foundation", - "url": "https://snapshot.org/#/s:lido-snapshot.eth/proposal/0xa478fa5518769096eda2b7403a1d4104ca47de3102e8a9abab8640ef1b50650c", - "type": "docs" - } - ] - } - ] - } - ] - } - ], - "lqty": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "LQTY holders' onchain power is voting on V2 Protocol Incentivized Liquidity (PIL) emissions. The rest of the protocol is described as \"Governance Free\" with immutable contracts and no admin keys, upgrade paths, or privileged roles.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "Neither LQTY holders nor the protocol team can influence core protocol execution - all core protocol parameters are immutable after launch. LQTY holders' onchain governance role is limited to voting on Protocol Incentivized Liquidity (PIL) emissions, directing a portion of V2 revenue to community-chosen liquidity initiatives.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity - Governance-Free", - "url": "https://www.liquity.org/features/governance-free", - "type": "docs" - }, - { - "name": "Directing Protocol Incentivized Liquidity with LQTY", - "url": "https://www.liquity.org/blog/directing-protocol-incentivized-liquidity-with-lqty", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "There are no privileged roles in the core protocol. All core protocol contracts are immutable after launch with no admin functions, owners, or upgrade keys.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity - Governance-Free", - "url": "https://www.liquity.org/features/governance-free", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "Core Liquity protocol contracts are non-upgradeable and do not use proxy patterns.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity v2 Technical Docs and Audits", - "url": "https://docs.liquity.org/v2-documentation/technical-docs-and-audits", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "The LQTY token contract is immutable with no proxy patterns or upgrade mechanisms.", - "evidence": [ - { - "urls": [ - { - "name": "LQTY Token Source", - "url": "https://etherscan.io/token/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "Fixed 100M LQTY token supply. No mint() function or inflation pathway in the bytecode.", - "evidence": [ - { - "urls": [ - { - "name": "The only LQTY token minting transactions ever, amounting to 100M LQTY tokens", - "url": "https://etherscan.io/advanced-filter?tkn=0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d&txntype=2&fadd=0x0000000000000000000000000000000000000000", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Access Gating", - "about": "Evaluates whether privileged roles can selectively restrict user access to the protocol.", - "status": "positive", - "notes": "No privileged roles. The only thing LQTY holders can do is vote for PIL emissions.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity - Governance-Free", - "url": "https://www.liquity.org/features/governance-free", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "No Guardian or blacklist capabilities exist in the LQTY token contract.", - "evidence": [ - { - "urls": [ - { - "name": "LQTY token's implementation code", - "url": "https://etherscan.io/address/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d#code", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "Stakers earn two live onchain streams: V1 protocol fees (ETH redemption fees + LUSD borrowing fees) routed directly to LQTYStaking, and V2 bribes paid pro-rata to voters who allocate their voting power to initiatives. There is no protocol treasury - V2 sends 100% of revenue straight to users. Fee parameters and revenue routing are immutable and cannot be modified by governance or the team.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "LQTY holders receive two live, onchain value streams.\n\nLiquity V1 - protocol revenue to stakers. Staked LQTY earns fees routed directly to the V1 LQTYStaking contract: redemption fees (paid in ETH) are forwarded by TroveManager, and borrowing fees (paid in LUSD) are forwarded by BorrowerOperations. Every staker accrues a pro-rata share via the F_ETH and F_LUSD accumulators - no voting required.\n\nLiquity V2 - bribes to voters. V2 governance is built on top of V1 staking, so V2 participants automatically receive the V1 fee streams above. Stakers who additionally allocate their voting power to an initiative can claim a pro-rata share of bribes (BOLD plus an initiative-specific token) deposited by external parties for that epoch.", - "evidence": [ - { - "urls": [ - { - "name": "TroveManager.sol - V1 redemption fee β†’ increaseF_ETH", - "url": "https://github.com/liquity/dev/blob/3e64ee1b52c50d51587c64c1cf75e0ba82934979/packages/contracts/contracts/TroveManager.sol#L1011-L1012", - "type": "github" - }, - { - "name": "BorrowerOperations.sol - V1 borrowing fee β†’ increaseF_LUSD", - "url": "https://github.com/liquity/dev/blob/3e64ee1b52c50d51587c64c1cf75e0ba82934979/packages/contracts/contracts/BorrowerOperations.sol#L370", - "type": "github" - }, - { - "name": "Liquity docs - LQTY staking (V1 revenue + V2 bribes)", - "url": "https://docs.liquity.org/v2-faq/lqty-staking", - "type": "docs" - }, - { - "name": "V2-gov Governance.sol - depositLQTY (V2 deposit stakes into V1)", - "url": "https://github.com/liquity/V2-gov/blob/main/src/Governance.sol#L162", - "type": "github" - }, - { - "name": "V2-gov Governance.sol - allocateLQTY (vote on initiatives)", - "url": "https://github.com/liquity/V2-gov/blob/main/src/Governance.sol#L584", - "type": "github" - }, - { - "name": "V2-gov BribeInitiative.sol - depositBribe / claimBribes", - "url": "https://github.com/liquity/V2-gov/blob/main/src/BribeInitiative.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "positive", - "notes": "There is no protocol treasury. Liquity V2 skips the concept of a centralized treasury and sends 100% of its revenue straight to its users.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity V2 Docs", - "url": "https://www.liquity.org/blog/liquity-v2-is-live", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "The V1 fee accrual mechanism is immutable - neither the core team nor LQTY holders can change the fee parameters or the routing of fees to the LQTYStaking contract, since the core protocol contracts are non-upgradeable and expose no admin or governance hooks over these parameters. V2 governance (PIL + bribes) lets LQTY voters direct a separate slice of V2 revenue to liquidity initiatives, but explicitly has no control over core protocol parameters, which are immutable after launch.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity v2 Technical Docs and Audits", - "url": "https://docs.liquity.org/v2-documentation/technical-docs-and-audits", - "type": "docs" - }, - { - "name": "Directing Protocol Incentivized Liquidity with LQTY", - "url": "https://www.liquity.org/blog/directing-protocol-incentivized-liquidity-with-lqty", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "tags": ["Reference"], - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "The protocol is entirely onchain - Aragon is not aware of any offchain entities towards which value accrues to the LQTY token or otherwise.", - "evidence": [] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "Both the LQTY token and core Liquity protocol contracts (V1 and V2) are open source on GitHub and source-verified against their onchain deployments - no closed-source components or unverified bytecode.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The LQTY token contract source code (LQTYToken.sol) is publicly available on GitHub and verified on Etherscan.", - "evidence": [ - { - "urls": [ - { - "name": "LQTY Token (Etherscan)", - "url": "https://etherscan.io/address/0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D#code", - "type": "explorer" - }, - { - "name": "LQTYToken.sol Source (GitHub)", - "url": "https://github.com/liquity/dev/blob/main/packages/contracts/contracts/LQTY/LQTYToken.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "Liquity protocol contracts are open source on GitHub.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity V2 Core (GitHub)", - "url": "https://github.com/liquity/bold", - "type": "github" - }, - { - "name": "Liquity V2 Governance (GitHub)", - "url": "https://github.com/liquity/V2-gov", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "LQTY supply is fully circulating - team and investor lockups ended in 2022, leaving only the immutable Stability Pool emission schedule for V1 depositors. Concentration among third parties has not yet been independently verified.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "unevaluated", - "notes": "Aragon has not yet verified that 3rd parties do not hold more than 50% of voting power", - "evidence": [] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "positive", - "notes": "It's all circulating - vesting ended in 2022, which can be verified by looking at the events emitted by the `LockupContractFactory`. The only remaining non-circulating LQTY is what the contract releases on an immutable schedule to Stability Pool depositors in V1.", - "evidence": [ - { - "urls": [ - { - "name": "LockupContractFactory (Etherscan)", - "url": "https://etherscan.io/address/0x2eBeF24dA09489218Ba2BECb01867F6DaAeDcD4B#code", - "type": "explorer" - }, - { - "name": "CommunityIssuance (Etherscan)", - "url": "https://etherscan.io/address/0xD8c9D9071123a059C6E0A945cF0e0c82b508d816#code", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Offchain dependencies for LQTY (trademark/brand, primary domain, and core software licensing) are controlled by Liquity AG, a Swiss company. LQTY tokenholders have no governance rights or control over Liquity AG. The core V1 protocol is immutable and governance-free, but brand, distribution, and V2 IP rights remain with the company.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "fail", - "notes": "The Liquity brand and related trademarks are owned and controlled by Liquity AG (Swiss company). No tokenholder-controlled legal entity is involved.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity AG - Crunchbase Company Profile", - "url": "https://www.crunchbase.com/organization/liquity-1d3e", - "type": "website" - }, - { - "name": "Liquity - Tracxn Company Profile", - "url": "https://tracxn.com/d/companies/liquity/__jQEYCp-QRDCuYvGmSSmUBbFIO6piP9rk6HEjxXEsrH0", - "type": "website" - }, - { - "name": "Team Page - Liquity.org", - "url": "https://www.liquity.org/team", - "type": "website" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "partial", - "notes": "Liquity AG controls the primary domain liquity.org and the frontend registry. However, the company explicitly does not run any user-facing frontend. All frontends are operated by independent third parties. No tokenholder-controlled entity controls distribution.", - "evidence": [ - { - "urls": [ - { - "name": "Liquity Runs on Decentralized Frontends - Official Blog", - "url": "https://www.liquity.org/blog/liquity-runs-on-decentralized-frontends", - "type": "website" - }, - { - "name": "Frontend Operators Page", - "url": "https://www.liquity.org/frontend-operators", - "type": "website" - }, - { - "name": "Liquity Launch Details (AG does not run frontend)", - "url": "https://www.liquity.org/blog/liquity-launch-details", - "type": "website" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "fail", - "notes": "Core protocol software and IP (V2) is owned by Liquity AG and released under a multi-year Business Source License (BUSL). Commercial deployments before ~September 2027 require approval from Liquity AG.", - "evidence": [ - { - "urls": [ - { - "name": "Licensing Liquity V2 - Official Blog", - "url": "https://www.liquity.org/blog/licensing-liquity-v2-may-the-fork-be-with-you", - "type": "website" - }, - { - "name": "Liquity V2 Core LICENSE File (GitHub)", - "url": "https://github.com/liquity/bold/blob/main/contracts/LICENSE", - "type": "github" - }, - { - "name": "What's New in Liquity V2 - Bankless", - "url": "https://www.bankless.com/read/whats-new-in-liquity-v2", - "type": "website" - } - ] - } - ] - } - ] - } - ], - "uni": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "UNI holders maintain ultimate control through onchain voting. The token is immutable with a hardcoded 2% annual inflation cap. All core contracts (V2, V3, V4) are non-upgradeable with no pause/freeze functions.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "UNI holders vote onchain via GovernorBravoDelegator, with execution through Timelock after a time delay.", - "evidence": [ - { - "urls": [ - { - "name": "GovernorBravoDelegator", - "url": "https://etherscan.io/address/0x408ed6354d4973f66138c91495f2f2fcbd8724c3#code", - "type": "explorer" - }, - { - "name": "Timelock", - "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "All critical roles flow through governance-controlled contracts, ensuring UNI holders maintain ultimate control.", - "evidence": [ - { - "name": "V2 Factory", - "summary": "feeToSetter is Timelock. Only feeToSetter can call setFeeTo() to change fee destination.", - "urls": [ - { - "name": "V2Factory", - "url": "https://etherscan.io/address/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f#code", - "type": "explorer" - } - ] - }, - { - "name": "V3 Factory", - "summary": "Owner is V3FeeAdapter, whose feeSetter is Timelock. Only Timelock can change V3 protocol fees.", - "urls": [ - { - "name": "V3Factory", - "url": "https://etherscan.io/address/0x1f98431c8ad98523631ae4a59f267346ea31f984#code", - "type": "explorer" - } - ] - }, - { - "name": "V4 PoolManager", - "summary": "Owner is Timelock. Only owner can call setProtocolFeeController().", - "urls": [ - { - "name": "V4PoolManager", - "url": "https://etherscan.io/address/0x000000000004444c5dc75cB358380D2e3dE08A90#code", - "type": "explorer" - } - ] - }, - { - "name": "UNI Token", - "summary": "Minter is set to Timelock. Since Timelock is controlled by GovernorBravoDelegator, all minting operations are controlled by UNI holders.", - "urls": [ - { - "name": "UNI Token", - "url": "https://etherscan.io/address/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984#code", - "type": "explorer" - }, - { - "name": "Timelock (deployed minter)", - "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "All Uniswap protocol contracts (V2 Pairs, V3 Pools, V4 PoolManager, Timelock) are non-upgradeable. Only GovernorBravoDelegator is upgradeable, controlled by Timelock.", - "evidence": [ - { - "name": "Non-upgradeable Protocol Contracts", - "summary": "V2 Pairs use Create2 deployment. V3 Pools use NoDelegateCall protection. V4 PoolManager uses Singleton design. All are immutable once deployed.", - "urls": [ - { - "name": "V2 Factory Create2", - "url": "https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol#L28-L31", - "type": "github" - }, - { - "name": "V3 Pool Protection", - "url": "https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L30", - "type": "github" - }, - { - "name": "V4 PoolManager: Singleton", - "url": "https://github.com/Uniswap/v4-core/blob/main/src/PoolManager.sol#L93", - "type": "github" - } - ] - }, - { - "name": "Upgradeable Governance Contract", - "summary": "GovernorBravoDelegator is upgradeable, with the Admin role held by the Timelock. Any upgrade would require a governance vote by UNI holders.", - "urls": [ - { - "name": "GovernorBravoDelegator", - "url": "https://etherscan.io/address/0x408ed6354d4973f66138c91495f2f2fcbd8724c3#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "Token contract is immutable with no proxy patterns, no delegatecall, no EIP-1967/UUPS patterns.", - "evidence": [ - { - "urls": [ - { - "name": "Uni.sol Source", - "url": "https://github.com/Uniswap/governance/blob/master/contracts/Uni.sol#L6", - "type": "github" - }, - { - "name": "Etherscan", - "url": "https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "UNI has a hardcoded 2% annual inflation cap that cannot be changed. Minting is controlled by Timelock (hence UNI holders).", - "evidence": [ - { - "name": "Inflation Cap", - "summary": "Mint can be called once per year, minting maximum 2% of total supply. mintCap is constant and cannot be changed.", - "urls": [ - { - "name": "Mint once per year logic", - "url": "https://github.com/Uniswap/governance/blob/eabd8c71ad01f61fb54ed6945162021ee419998e/contracts/Uni.sol#L116", - "type": "github" - }, - { - "name": "2% mint cap", - "url": "https://github.com/Uniswap/governance/blob/eabd8c71ad01f61fb54ed6945162021ee419998e/contracts/Uni.sol#L120", - "type": "github" - } - ] - }, - { - "name": "Minting Control", - "summary": "Minter is set to Timelock, governed by UNI token holders through GovernorBravoDelegator.", - "urls": [ - { - "name": "Timelock", - "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "positive", - "notes": "No pause/freeze functions in V2 Pair, V3 Pool, or V4 PoolManager core contracts. No admin backdoors to steal funds. No emergency withdrawal or sweep functions.", - "evidence": [] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "No blacklist, whitelist, or pause functions in UNI token contract. Transfers cannot be censored. Standard ERC20 with no admin controls over transfers.", - "evidence": [ - { - "urls": [ - { - "name": "Uni.sol", - "url": "https://github.com/Uniswap/governance/blob/master/contracts/Uni.sol#L6", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "Protocol fees flow to governance-controlled destinations through TokenJar and FirePit burn mechanism. Treasury is entirely controlled by token holders.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "Protocol fees across all Uniswap versions flow to governance-controlled destinations (TokenJar). Value accrual is via burn mechanism - a burn can be initiated by anyone who chooses to burn 4,000 UNI to claim accumulated protocol fees from TokenJar via FirePit.", - "evidence": [ - { - "name": "V2 Fees", - "summary": "All V2 pools are active. Fees go to FeeTo address which is set to TokenJar.", - "urls": [ - { - "name": "UniswapV2Pool: feeTo", - "url": "https://etherscan.io/address/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f#readContract", - "type": "explorer" - }, - { - "name": "UniswapV2Pool: Fee Destination", - "url": "https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L90", - "type": "github" - } - ] - }, - { - "name": "V3 Fees", - "summary": "V3FeeAdapter collects protocol fees by calling collectProtocol and sends them to TokenJar. The 4,000 UNI threshold can be changed by governance via Timelock in FirePit. Only select V3 pools are currently active.", - "urls": [ - { - "name": "V3FeeAdapter", - "url": "https://etherscan.io/address/0x5E74C9f42EEd283bFf3744fBD1889d398d40867d#code", - "type": "explorer" - }, - { - "name": "UniswapV3Pool: collectProtocol", - "url": "https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L848", - "type": "github" - } - ] - }, - { - "name": "V4 Fees", - "summary": "V4 fees have not been activated yet, but in the future fees can be activated via governance process as described in Onchain Governance Workflow. To activate fees, Timelock will set the PoolManager's ProtocolFeeController address to a V4FeeAdapter contract which contains logic for fee collection and distribution.", - "urls": [ - { - "name": "V4 PoolManager", - "url": "https://etherscan.io/address/0x000000000004444c5dc75cB358380D2e3dE08A90#code", - "type": "explorer" - }, - { - "name": "V4 PoolManager: CollectProtocolFees", - "url": "https://github.com/Uniswap/v4-core/blob/d153b048868a60c2403a3ef5b2301bb247884d46/src/ProtocolFees.sol#L48", - "type": "github" - } - ] - }, - { - "name": "Burn Mechanism", - "summary": "The 4,000 UNI threshold can be changed by governance via Timelock in FirePit.", - "urls": [ - { - "name": "FirePit 4000 UNI Requirement", - "url": "https://github.com/Uniswap/protocol-fees/blob/8604e4b9aed88bdd6be3a322e19722c40f94be2c/src/releasers/ExchangeReleaser.sol#L35", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "positive", - "notes": "The Uniswap Governance Timelock, often referred to as the Treasury, is entirely controlled by token holders. At the time of writing, it held approximately 264m UNI tokens. Funds held in Timelock require UNI governance proposals to execute transfers, ensuring tokenholder control.", - "evidence": [ - { - "urls": [ - { - "name": "Timelock", - "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "Token holders control fee routing and burn threshold across all versions through Timelock-governed ownership.", - "evidence": [ - { - "name": "V2 Fee Control", - "summary": "feeToSetter is set to Timelock on UniswapV2Factory. Only feeToSetter can call setFeeTo to change the fee destination. Protocol fees can only be enabled or disabled, not adjusted. Protocol fees on V2 pools cannot be adjusted on a per pool basis.", - "urls": [ - { - "name": "Timelock", - "url": "https://etherscan.io/address/0x1a9C8182C09F50C8318d769245beA52c32BE35BC", - "type": "explorer" - }, - { - "name": "UniswapV2Factory", - "url": "https://etherscan.io/address/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f", - "type": "explorer" - } - ] - }, - { - "name": "V3 Fee Control", - "summary": "Owner of UniswapV3Factory is V3FeeAdapter, whose owner is Timelock. V3FeeAdapter's owner can call setOwner on V3FeeAdapter, which passes the call through to UniswapV3Factory. Using this method, V3FeeAdapter's owner can change the owner of the UniswapV3Factory to a new V3FeeAdapter. The pools collecting protocol fees as well as the protocol fee percentage per pool can be changed by the pool owner via setFeeProtocol", - "urls": [ - { - "name": "UniswapV3Factory", - "url": "https://etherscan.io/address/0x1f98431c8ad98523631ae4a59f267346ea31f984", - "type": "explorer" - }, - { - "name": "UniswapV3Pool: setFeeProtocol", - "url": "https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L837", - "type": "github" - } - ] - }, - { - "name": "V4 Fee Control", - "summary": "Owner of V4PoolManager is set to Timelock. Only owner can call setProtocolFeeController to change the fee controller. Protocol fees can be modified by the ProtocolFeeController through setProtocolFee.", - "urls": [ - { - "name": "PoolManager(V4): setProtocolFee", - "url": "https://github.com/Uniswap/v4-core/blob/d153b048868a60c2403a3ef5b2301bb247884d46/src/ProtocolFees.sol#L35", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "DUNI (Decentralized Unincorporated Nonprofit Association) provides legal capacity for the DAO to engage in off-chain activities such as tax compliance, legal defense, and contract execution. At the time of writing, there are no specific offchain value accrual mechanisms explicitly defined.", - "evidence": [ - { - "urls": [ - { - "name": "DUNI Proposal", - "url": "https://gov.uniswap.org/t/governance-proposal-establish-uniswap-governance-as-duni-a-wyoming-duna/25770", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "UNI token source is publicly available on GitHub and verified on Etherscan. Uniswap V2, V3, V4, and UniswapX protocol contracts are open source and verified.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The UNI token contract source code (Uni.sol) is publicly available on GitHub and verified on Etherscan.", - "evidence": [ - { - "urls": [ - { - "name": "UNI Token (Etherscan)", - "url": "https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984#code", - "type": "explorer" - }, - { - "name": "Uni.sol Source (GitHub)", - "url": "https://github.com/Uniswap/governance/blob/master/contracts/Uni.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "Uniswap V2, V3, V4, and UniswapX protocol contracts are open source on GitHub.", - "evidence": [ - { - "urls": [ - { - "name": "Uniswap V2 Core (GitHub)", - "url": "https://github.com/Uniswap/v2-core", - "type": "github" - }, - { - "name": "Uniswap V3 Core (GitHub)", - "url": "https://github.com/Uniswap/v3-core", - "type": "github" - }, - { - "name": "Uniswap V4 Core (GitHub)", - "url": "https://github.com/Uniswap/v4-core", - "type": "github" - }, - { - "name": "UniswapX (GitHub)", - "url": "https://github.com/Uniswap/UniswapX", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "unevaluated", - "notes": "Aragon has not yet verified that 3rd parties do not hold more than 50% of voting power", - "evidence": [] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "positive", - "notes": "Four vesting contracts distributed UNI to the Governance Timelock over four years, ending in September 2024. There are no future unlocks.", - "evidence": [ - { - "name": "TreasuryVester Contracts", - "summary": "UNI tokens were initially minted to an EOA, which transferred the Treasury's portion to four vesting contracts in varying amounts. Each TreasuryVester contract was configured with: uni, recipient, vestingAmount, vestingBegin, vestingCliff, vestingEnd.", - "urls": [ - { - "name": "TreasuryVester Code", - "url": "https://github.com/Uniswap/governance/blob/master/contracts/TreasuryVester.sol", - "type": "github" - }, - { - "name": "TreasuryVester 1", - "url": "https://etherscan.io/address/0x4750c43867ef5f89869132eccf19b9b6c4286e1a", - "type": "explorer" - }, - { - "name": "TreasuryVester 2", - "url": "https://etherscan.io/address/0xe3953d9d317b834592ab58ab2c7a6ad22b54075d", - "type": "explorer" - }, - { - "name": "TreasuryVester 3", - "url": "https://etherscan.io/address/0x4b4e140d1f131fdad6fb59c13af796fd194e4135", - "type": "explorer" - }, - { - "name": "TreasuryVester 4", - "url": "https://etherscan.io/address/0x3d30b1ab88d487b0f3061f40de76845bec3f1e94", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Core trademark rights (UNISWAP, UNI, UNISWAP LABS) are held by Universal Navigation Inc. (Uniswap Labs). DUNI has a limited, non-exclusive trademark license but no ownership transfer has occurred. Uniswap Labs operates a user interface and API, but the permissionless smart contracts allow anybody to operate one.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Core trademark rights related to Uniswap Labs, including UNISWAP, UNI, and UNISWAP LABS, are held and enforceable by Universal Navigation Inc. d/b/a Uniswap Labs (\"Labs\").\n\nAfter the passing of the UNIfication proposal, in which Uniswap Labs entered into a service provider agreement with DUNI (formerly Uniswap Governance), Labs grants DUNI a non-exclusive, royalty-free, worldwide trademark license to use the UNISWAP mark solely in connection with DUNI's business, subject to Labs' trademark usage guidelines and limited to the term of the agreement.\n\nThe agreement does not transfer ownership of the UNISWAP mark or Labs' other trademarks to DUNI or token holders.\n\nAny broader assignment, or permanent or exclusive license, of Labs' trademarks to DUNI is explicitly deferred and would require a separate written agreement negotiated in the future.", - "evidence": [ - { - "urls": [ - { - "name": "Uniswap Labs Trademark Guidelines", - "url": "https://support.uniswap.org/hc/en-us/articles/30934762216973-Uniswap-Labs-Trademark-Guidelines/", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Uniswap Labs operates a user interface and API, both of which are subject to Terms of Service. The permissionless nature of the Uniswap Protocol’s smart contracts means that any person or firm can operate an interface or API.", - "evidence": [ - { - "urls": [ - { - "name": "Uniswap Labs Terms of Service", - "url": "https://support.uniswap.org/hc/en-us/articles/30935100859661-Uniswap-Labs-Terms-of-Service", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Uniswap Labs retains ownership of its pre-existing IP and trademarks. DUNI holds a limited, non-exclusive license to use the UNISWAP trademark for DUNI-related purposes, and generally receives rights to new deliverables under open-source licenses. No transfer of core Labs' IP has occurred; any broader licensing or assignment would require a separate agreement.", - "evidence": [] - } - ] - } - ], - "yb": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "veYB holders control the protocol through Aragon governance. The YB token is non-upgradeable with renounced ownership. Protocol upgrades are controlled by the DAO through MigrationFactoryOwner. The veYB contract owner (_yb.eth) can change transfer clearance rules, but this does not constitute censorshipβ€”users remain custodians of their locked YB.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "veYB holders vote onchain via Aragon governance DAO. Proposals require **30% participation**, **55% support**, and a **7-day voting period**. Proposals can be executed before the full voting period concludes when mathematical certainty is achieved (thresholds met, remaining votes cannot change outcome). Minimum 1 veYB required to create proposals.", - "evidence": [ - { - "name": "Governance System", - "summary": "Aragon governance DAO with TokenVoting Plugin. veYB implements standard IVotes interface for Aragon compatibility.", - "urls": [ - { - "name": "DAO Contract", - "url": "https://etherscan.io/address/0x42F2A41A0D0e65A440813190880c8a65124895Fa", - "type": "explorer" - }, - { - "name": "veYB (VotingEscrow)", - "url": "https://etherscan.io/address/0x8235c179E9e84688FBd8B12295EfC26834dAC211", - "type": "explorer" - }, - { - "name": "TokenVoting Plugin", - "url": "https://etherscan.io/address/0x2be6670DE1cCEC715bDBBa2e3A6C1A05E496ec78", - "type": "explorer" - }, - { - "name": "Governance Documentation", - "url": "https://docs.yieldbasis.com/user/governance", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "**DAO-controlled:** GaugeController, FeeDistributor, MigrationFactoryOwner (ADMIN immutable = DAO).\n\n**EOA-controlled:** veYB owner (_yb.eth) can change transfer clearance rules. However, transfer restrictions do not constitute censorshipβ€”users remain custodians of their locked YB and can always withdraw after lock expiry.", - "evidence": [ - { - "name": "DAO-Controlled Contracts", - "summary": "GaugeController.owner() = DAO. FeeDistributor.owner() = DAO. MigrationFactoryOwner.ADMIN = DAO (immutable).", - "urls": [ - { - "name": "GaugeController", - "url": "https://etherscan.io/address/0x1Be14811A3a06F6aF4fA64310a636e1Df04c1c21", - "type": "explorer" - }, - { - "name": "FeeDistributor", - "url": "https://etherscan.io/address/0xD11b416573EbC59b6B2387DA0D2c0D1b3b1F7A90", - "type": "explorer" - }, - { - "name": "MigrationFactoryOwner", - "url": "https://etherscan.io/address/0xa68343ed4d517a277cfa1f2fc2b51f7a6794b6ad", - "type": "explorer" - } - ] - }, - { - "name": "veYB Owner (_yb.eth)", - "summary": "Can call set_transfer_clearance_checker() to change veYB transfer rules. This restricts veNFT transfers, NOT custodyβ€”users remain owners of their locked YB.", - "urls": [ - { - "name": "veYB Contract", - "url": "https://etherscan.io/address/0x8235c179E9e84688FBd8B12295EfC26834dAC211", - "type": "explorer" - }, - { - "name": "set_transfer_clearance_checker", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/VotingEscrow.vy#L640-L647", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "The DAO controls protocol upgrades through MigrationFactoryOwner. This constitutes ownershipβ€”the indirection through governance is the standard mechanism for tokenholder control.", - "evidence": [ - { - "name": "Upgrade Path", - "summary": "Factory.set_implementations() requires Factory admin = MigrationFactoryOwner. MigrationFactoryOwner requires ADMIN (immutable) = DAO.", - "urls": [ - { - "name": "Factory Contract", - "url": "https://etherscan.io/address/0x370a449FeBb9411c95bf897021377fe0B7D100c0", - "type": "explorer" - }, - { - "name": "Factory.set_implementations", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/Factory.vy#L389-L410", - "type": "github" - }, - { - "name": "MigrationFactoryOwner ADMIN immutable", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/MigrationFactoryOwner.vy#L47", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "YB token is a **non-upgradeable** Vyper contract. Ownership has been **renounced** (owner = 0x0). No proxy pattern. Only GaugeController (set before renouncement) can mint via emit().", - "evidence": [ - { - "name": "Token Ownership", - "summary": "owner() returns 0x0. Token uses standard ERC-20 implementation with renounced ownership at deployment.", - "urls": [ - { - "name": "YB Token Contract", - "url": "https://etherscan.io/address/0x01791F726B4103694969820be083196cC7c045fF#code", - "type": "explorer" - }, - { - "name": "renounce_ownership", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy#L90-L100", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "**1B max supply** with programmatic emission. Only GaugeController can mint. Emission rate determined by gauge staking levels: get_adjustment() returns sqrt(staked / totalSupply), so higher LP staking = higher emission rate (up to 100% of max rate).", - "evidence": [ - { - "name": "Emission Mechanism", - "summary": "GaugeController calls YB.emit() with rate_factor based on LP staking levels. No discretionary minting possible.", - "urls": [ - { - "name": "get_adjustment (minting rate)", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/LiquidityGauge.vy#L133-L142", - "type": "github" - }, - { - "name": "YB.emit function", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy#L103-L122", - "type": "github" - }, - { - "name": "Tokenomics", - "url": "https://docs.yieldbasis.com/user/tokenomics", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "positive", - "notes": "User exits are **permissionless**: veYB withdrawal after lock expiry, fee claims. Gauge killing is DAO-controlled and affects emissions, not user funds.", - "evidence": [ - { - "name": "Exit Paths", - "summary": "VotingEscrow.withdraw() permissionless after lock expiry. FeeDistributor.claim() has no admin check.", - "urls": [ - { - "name": "VotingEscrow.withdraw", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/VotingEscrow.vy#L428-L457", - "type": "github" - }, - { - "name": "FeeDistributor.claim", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/FeeDistributor.vy#L269-L277", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "**No blacklist, freeze, or seizure functions** in YB token. The veYB transfer clearance checker can restrict veNFT transfers, but this does not censor the underlying YB tokenβ€”users remain custodians and can withdraw after lock expiry.", - "evidence": [ - { - "urls": [ - { - "name": "YB.vy full source", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "veYB holders receive **protocol admin fees** (a subset of protocol revenue) via FeeDistributor. Fees originate from LT (Liquidity Token) vault admin fees, distributed weekly in yb-LP tokens. DAO controls fee direction via MigrationFactoryOwner, and gauge voting directs YB emissions. The Ecosystem Reserve is controlled by an EOA, not the DAO.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "FeeDistributor is **actively distributing** yb-cbBTC, yb-WBTC, yb-tBTC, and yb-WETH LP tokens to veYB holders.\n\n**Important distinction:** Protocol revenue = LP fees + position rebalancing expenses. What veYB holders receive is the **protocol admin fee** (admin_fee)β€”a subset of protocol revenue, not the total.\n\n**Fee flow:** LT vaults accrue admin fees β†’ anyone calls withdraw_admin_fees() β†’ mints LT to fee_receiver (FeeDistributor) β†’ fill_epochs() distributes over 4 weeks β†’ veYB holders claim pro-rata.\n\n**Data source:** [ValueVerse](https://yb.valueverse.ai)", - "evidence": [ - { - "name": "Fee Flow", - "summary": "Admin fees from LT vaults flow to FeeDistributor via Factory.fee_receiver. Distribution is automatic and weekly over 4 epochs.", - "urls": [ - { - "name": "LT.withdraw_admin_fees", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/LT.vy#L866-L896", - "type": "github" - }, - { - "name": "Factory.fee_receiver", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/Factory.vy#L98", - "type": "github" - }, - { - "name": "FeeDistributor._fill_epochs (OVER_WEEKS=4)", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/FeeDistributor.vy#L86-L107", - "type": "github" - }, - { - "name": "FeeDistributor Contract", - "url": "https://etherscan.io/address/0xD11b416573EbC59b6B2387DA0D2c0D1b3b1F7A90", - "type": "explorer" - }, - { - "name": "veYB Documentation", - "url": "https://docs.yieldbasis.com/user/veyb", - "type": "docs" - }, - { - "name": "Fee Data (ValueVerse)", - "url": "https://yb.valueverse.ai", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "at_risk", - "notes": "The **Ecosystem Reserve** is a VestingEscrow controlled by an EOA, not the DAO. This is the largest pool of discretionary YB outside the vesting contracts. The DAO holds a small amount of YB directly.", - "evidence": [ - { - "name": "Ecosystem Reserve", - "summary": "Ecosystem Reserve is a VestingEscrow controlled by an EOA, not the DAO.", - "urls": [ - { - "name": "Ecosystem Reserve", - "url": "https://etherscan.io/address/0x7aC5922776034132D9ff5c7889d612d98e052Cf2", - "type": "explorer" - }, - { - "name": "Ecosystem Reserve Owner (EOA)", - "url": "https://etherscan.io/address/0xC1671c9efc9A2ecC347238BeA054Fc6d1c6c28F9", - "type": "explorer" - }, - { - "name": "DAO Contract", - "url": "https://etherscan.io/address/0x42F2A41A0D0e65A440813190880c8a65124895Fa", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "veYB holders control both the **direction of automated value flows** and **emission routing**:\n\n**Fee direction:** DAO controls Factory.fee_receiver via MigrationFactoryOwner, determining where admin fees are routed.\n\n**Gauge voting = fee control by veYB:** veYB holders vote on gauge weights. Higher weight = more YB emissions to that pool's stakers, incentivizing LP staking which generates the admin fees distributed back to veYB holders. 10-day vote lock prevents manipulation.", - "evidence": [ - { - "name": "Fee Flow Direction", - "summary": "DAO controls Factory.fee_receiver via MigrationFactoryOwner.set_fee_receiver(). This determines where admin fees are routed.", - "urls": [ - { - "name": "MigrationFactoryOwner.set_fee_receiver", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/MigrationFactoryOwner.vy#L143-L145", - "type": "github" - }, - { - "name": "Factory.set_fee_receiver", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/Factory.vy#L358-L364", - "type": "github" - } - ] - }, - { - "name": "Gauge Voting", - "summary": "veYB holders vote on gauge weights to direct YB emissions. This incentivizes LP staking, generating admin fees that flow back to veYB holders.", - "urls": [ - { - "name": "vote_for_gauge_weights", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/GaugeController.vy#L206-L287", - "type": "github" - }, - { - "name": "GaugeController Contract", - "url": "https://etherscan.io/address/0x1Be14811A3a06F6aF4fA64310a636e1Df04c1c21", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "tags": ["Reference"], - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "Aragon has not been able to verify whether YieldBasis AG (operating entity) provides any offchain value to YB token holders. No evidence of equity linkage, offchain revenue sharing, or legal commitments to tokenholders.", - "evidence": [] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "All core contracts are verified on Etherscan with matching GitHub source (Vyper 0.4.3). Protocol has undergone 6 independent security audits (Statemind, Chainsecurity, Quantstamp, Mixbytes, Electisec, Pashov).", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "YB token is **verified on Etherscan** (Vyper 0.4.3, AGPL v3.0 license). Source code available on GitHub with matching bytecode.", - "evidence": [ - { - "urls": [ - { - "name": "YB Token Verified on Etherscan", - "url": "https://etherscan.io/address/0x01791F726B4103694969820be083196cC7c045fF#code", - "type": "explorer" - }, - { - "name": "YB.vy Source", - "url": "https://github.com/yield-basis/yb-core/blob/41137e5837e411c9d60be8705ca74304b082fa92/contracts/dao/YB.vy", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "All core contracts (Factory, veYB, GaugeController, FeeDistributor) are **verified on Etherscan**. **6 independent security audits** completed between March-August 2025.", - "evidence": [ - { - "name": "Verification and Audits", - "summary": "Audited by Statemind (Feb-May 2025), Chainsecurity (Jul 2025), Quantstamp (Apr 2025), Mixbytes (Aug 2025), Electisec (Aug 2025), and Pashov (Mar-Apr 2025).", - "urls": [ - { - "name": "yb-core GitHub Repository", - "url": "https://github.com/yield-basis/yb-core", - "type": "github" - }, - { - "name": "Audit Reports", - "url": "https://docs.yieldbasis.com/user/audits-bug-bounties", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "~77M YB locked as veYB (~10% of minted supply). Team (25%) + Investors (12.1%) have 24-month vesting with a 6-month cliff ending March 2026, after which 25% unlocks and the remaining 75% vests linearly over 18 months.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "warning", - "notes": "~77M veYB locked from ~722M minted YB (~10% participation). Post-cliff, **Team (250M) + Investors (121M) = 371M YB (37%)** could potentially influence governance if coordinated, given low current veYB participation.", - "evidence": [ - { - "name": "veYB Supply", - "summary": "veYB supply ~77M tokens locked. Low participation rate means large token holders have outsized influence.", - "urls": [ - { - "name": "veYB Contract", - "url": "https://etherscan.io/address/0x8235c179E9e84688FBd8B12295EfC26834dAC211", - "type": "explorer" - }, - { - "name": "Tokenomics", - "url": "https://docs.yieldbasis.com/user/tokenomics", - "type": "docs" - } - ] - } - ] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "warning", - "notes": "**24-month vesting** with **6-month cliff** ending ~March 15, 2026.\n\n**During cliff:** Cannot sell, but **can lock into veYB**. ~35M YB was locked by team and investors during the cliff, choosing protocol fees over liquidity.\n\n**At cliff end:** 25% unlocks (6/24 months).\n\n**Post-cliff:** Remaining 75% vests linearly over 18 months until September 2027.\n\n**Affected:** Team (250M) + Investors (121M) = 371M YB (37% of max supply). Tokens release graduallyβ€”they cannot be instantly used to influence governance.", - "evidence": [ - { - "name": "Vesting Schedule", - "summary": "Start: Sept 15, 2025. Cliff: 6 months = March 15, 2026 (25% unlocks). Linear vesting: 18 months post-cliff (75% remaining). Full vest: Sept 15, 2027.", - "urls": [ - { - "name": "Tokenomics", - "url": "https://docs.yieldbasis.com/user/tokenomics", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Aragon has not been able to verify trademark ownership. Aragon has not been able to identify published terms of service or a contracting entity for the primary distribution channels. DAO contracts use AGPL v3.0 (open source), but Factory uses proprietary license.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "unevaluated", - "notes": "Aragon has not been able to verify trademark ownership.", - "evidence": [] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Aragon has not been able to identify published terms of service or a contracting entity for the primary distribution channels.", - "evidence": [ - { - "urls": [ - { - "name": "YieldBasis Website", - "url": "https://yieldbasis.com/", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "**Mixed licensing**: YB.vy, VotingEscrow.vy, GaugeController.vy use AGPL v3.0 (open source). **Factory.vy uses 'Copyright (c) 2025'** (proprietary).", - "evidence": [ - { - "name": "License Analysis", - "summary": "DAO contracts (YB, veYB, GaugeController, FeeDistributor): AGPL v3.0. Factory, MigrationFactoryOwner: 'Copyright (c) 2025' (proprietary).", - "urls": [ - { - "name": "yb-core contracts directory", - "url": "https://github.com/yield-basis/yb-core/tree/41137e5837e411c9d60be8705ca74304b082fa92/contracts", - "type": "github" - }, - { - "name": "Curve Licensing Vesting", - "url": "https://etherscan.io/address/0x36e36D5D588D480A15A40C7668Be52D36eb206A8", - "type": "explorer" - } - ] - } - ] - } - ] - } - ], - "sky": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "SKY holders exercise binding governance through an approval-based voting workflow with a 24-hour timelock before any changes take effect. The token is non-upgradeable with no censorship capabilities. All privileged roles trace back to governance.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "positive", - "notes": "SKY holders vote to elect \"spells\" (upgrade contracts). The winning spell must wait 24 hours before it can execute changes. This creates binding, verifiable onchain governance.", - "evidence": [ - { - "name": "Governance Workflow", - "summary": "How votes become protocol changes:\n\n1. Lock SKY tokens in voting contract\n2. Vote for a \"spell\" (upgrade contract)\n3. Winning spell schedules execution via plot()\n4. 24-hour delay enforced (eta >= now + 86400s)\n5. After delay, anyone can trigger exec()\n\nThe plot() function enforces the delay β€” no spell can execute without waiting.", - "urls": [ - { - "name": "Voting Contract", - "url": "https://etherscan.io/address/0x929d9A1435662357F54AdcF64DcEE4d6b867a6f9#code", - "type": "explorer" - }, - { - "name": "plot() delay enforcement (L111-116)", - "url": "https://github.com/sky-ecosystem/ds-pause/blob/master/src/pause.sol#L111-L116", - "type": "github" - } - ] - }, - { - "name": "Timelock Contract", - "summary": "All governance actions execute through a timelock (Pause Proxy). The 24-hour delay gives users time to exit before changes take effect.", - "urls": [ - { - "name": "Pause Proxy (Etherscan)", - "url": "https://etherscan.io/address/0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB#code", - "type": "explorer" - }, - { - "name": "exec() function (L124-136)", - "url": "https://github.com/sky-ecosystem/ds-pause/blob/master/src/pause.sol#L124-L136", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "positive", - "notes": "All privileged roles trace back to SKY governance. A single \"Pause Proxy\" contract holds admin rights on all core contracts. Only governance-approved spells can act through it.", - "evidence": [ - { - "name": "Core Contracts Controlled by Governance", - "summary": "The Pause Proxy holds admin rights on all key contracts. Verify by calling wards(0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB) on each contract β€” returns 1 if authorized.", - "urls": [ - { - "name": "Vat wards() β€” verify Pause Proxy", - "url": "https://etherscan.io/address/0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B#readContract", - "type": "explorer" - }, - { - "name": "Vow wards() β€” verify Pause Proxy", - "url": "https://etherscan.io/address/0xA950524441892A31ebddF91d3cEEFa04Bf454466#readContract", - "type": "explorer" - }, - { - "name": "SKY wards() β€” verify Pause Proxy", - "url": "https://etherscan.io/address/0x56072C95FAA701256059aa122697B133aDEd9279#readContract", - "type": "explorer" - } - ] - }, - { - "name": "Emergency Controls", - "summary": "\"Mom\" contracts can adjust parameters without the 24hr delay for emergencies. These are governance-controlled via the authority pattern.", - "urls": [ - { - "name": "Mom authority pattern (source)", - "url": "https://github.com/sky-ecosystem/osm-mom/blob/master/src/OsmMom.sol#L55", - "type": "github" - }, - { - "name": "OSM_MOM β€” verify authority()", - "url": "https://etherscan.io/address/0x76416A4d5190d071bfed309861527431304aA14f#readContract", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "positive", - "notes": "Core accounting contracts are immutable β€” their code cannot change. Newer stablecoin contracts (USDS, sUSDS) can be upgraded, but only through governance.", - "evidence": [ - { - "name": "Immutable Core", - "summary": "Core accounting contracts have no upgrade mechanism. The deployed code is permanent.", - "urls": [ - { - "name": "Vat source code", - "url": "https://github.com/sky-ecosystem/dss/blob/master/src/vat.sol", - "type": "github" - }, - { - "name": "SKY token source code", - "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol", - "type": "github" - } - ] - }, - { - "name": "Upgradeable Stablecoins", - "summary": "USDS and sUSDS use proxy contracts. Upgrades require governance approval through the standard voting process.", - "urls": [ - { - "name": "USDS upgrade authorization", - "url": "https://github.com/sky-ecosystem/usds/blob/master/src/Usds.sol#L73", - "type": "github" - }, - { - "name": "sUSDS source code", - "url": "https://github.com/sky-ecosystem/stusds/blob/master/src/StUsds.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "The SKY token cannot be upgraded. Its code is permanent with no admin backdoors.", - "evidence": [ - { - "name": "Non-Upgradeable Token", - "summary": "Standard ERC-20 contract with no proxy pattern. The deployed code cannot be changed.", - "urls": [ - { - "name": "SKY Token Contract", - "url": "https://etherscan.io/address/0x56072C95FAA701256059aa122697B133aDEd9279#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "Only governance can mint new SKY. The primary source is conversion from legacy MKR tokens at a fixed 24,000:1 ratio. Anyone can burn their own tokens.", - "evidence": [ - { - "name": "Minting Restricted to Governance", - "summary": "The mint() function requires authorization. Only governance-controlled contracts can call it.", - "urls": [ - { - "name": "mint() function (L146-154)", - "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol#L146-L154", - "type": "github" - } - ] - }, - { - "name": "MKR Conversion", - "summary": "Legacy MKR tokens convert to SKY at a fixed 24,000:1 ratio. The rate is immutable in the contract.", - "urls": [ - { - "name": "rate() returns 24000", - "url": "https://etherscan.io/address/0xA1Ea1bA18E88C381C724a75F23a130420C403f9a#readContract", - "type": "explorer" - }, - { - "name": "Immutable rate declaration", - "url": "https://github.com/sky-ecosystem/sky/blob/master/src/MkrSky.sol#L36", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "positive", - "notes": "No admin can arbitrarily pause the protocol or restrict user access. Emergency shutdown exists but requires burning a significant amount of tokens.", - "evidence": [ - { - "name": "Emergency Shutdown", - "summary": "The only way to \"pause\" the protocol is Emergency Shutdown, which requires burning tokens past a threshold. This is a nuclear option, not an admin switch.", - "urls": [ - { - "name": "ESM source code", - "url": "https://github.com/sky-ecosystem/esm/blob/master/src/ESM.sol", - "type": "github" - }, - { - "name": "ESM contract on Etherscan", - "url": "https://etherscan.io/address/0x09e05fF6142F2f9de8B6B65855A1d56B6cfE4c58#code", - "type": "explorer" - } - ] - }, - { - "name": "No Arbitrary Pause", - "summary": "Core contracts have no pause function. Users can always interact with the protocol.", - "urls": [] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "SKY has no blacklist, freeze, or admin-controlled transfer blocking. Tokens cannot be seized or frozen by anyone.", - "evidence": [ - { - "name": "No Censorship Functions", - "summary": "The contract has no blacklist, freeze, or pause functions. Transfer functions are standard ERC-20 with no admin intervention points.", - "urls": [ - { - "name": "Transfer Functions (L96-135)", - "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol#L96-L135", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "Protocol revenue flows to SKY holders through automated buybacks. When the protocol earns fees, it uses them to buy SKY on the open market. All fee parameters are governance-controlled.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "A portion of protocol revenue (interest on loans, liquidation penalties) automatically flows to buybacks. The \"Smart Burn Engine\" purchases SKY using accumulated fees.", - "evidence": [ - { - "name": "Smart Burn Engine", - "summary": "Automated buyback system. When protocol surplus exceeds the buffer threshold, the Splitter routes fees to the Flapper, which purchases SKY on UniswapV2.", - "urls": [ - { - "name": "Splitter contract", - "url": "https://etherscan.io/address/0xBF7111F13386d23cb2Fba5A538107A73f6872bCF#code", - "type": "explorer" - }, - { - "name": "Flapper (buyback executor)", - "url": "https://etherscan.io/address/0x374D9c3d5134052Bc558F432Afa1df6575f07407#code", - "type": "explorer" - }, - { - "name": "Flapper source code", - "url": "https://github.com/sky-ecosystem/dss-flappers/blob/master/src/FlapperUniV2.sol", - "type": "github" - }, - { - "name": "Smart Burn Engine Dashboard", - "url": "https://info.sky.money/smart-burn-engine", - "type": "docs" - } - ] - }, - { - "name": "Revenue Flow", - "summary": "Protocol revenue flows: Stability Fees β†’ Vow (surplus) β†’ Splitter β†’ Flapper β†’ SKY buybacks.\n\nRevenue sources:\n- Interest on loans (stability fees)\n- Liquidation penalties\n- Stablecoin swap fees (PSM)", - "urls": [ - { - "name": "Stability fee accrual", - "url": "https://github.com/sky-ecosystem/dss/blob/master/src/jug.sol#L122-L128", - "type": "github" - }, - { - "name": "Surplus routing to flapper", - "url": "https://github.com/sky-ecosystem/dss/blob/master/src/vow.sol#L148-L152", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "positive", - "notes": "All treasury assets are held onchain and controlled by governance. There is no offchain treasury or multi-sig.", - "evidence": [ - { - "name": "Onchain Treasury", - "summary": "Protocol surplus is held in the Vow contract. Only governance can access these funds.", - "urls": [ - { - "name": "Treasury Contract", - "url": "https://etherscan.io/address/0xA950524441892A31ebddF91d3cEEFa04Bf454466#readContract", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "positive", - "notes": "Governance controls all fee rates and revenue parameters. SKY holders decide how much the protocol charges and how revenue is distributed.", - "evidence": [ - { - "name": "Fee Rate Control", - "summary": "Governance sets stability fee rates via the Jug contract. Each collateral type has its own rate, updated through governance spells.", - "urls": [ - { - "name": "Jug fee configuration", - "url": "https://github.com/sky-ecosystem/dss/blob/master/src/jug.sol#L107-L111", - "type": "github" - }, - { - "name": "Jug contract", - "url": "https://etherscan.io/address/0x19c0976f590D67707E62397C87829d896Dc0f1F1#code", - "type": "explorer" - } - ] - }, - { - "name": "Surplus Buffer Control", - "summary": "The surplus buffer (\"hump\") determines when buybacks trigger. Governance can adjust this threshold to control the pace of buybacks.", - "urls": [ - { - "name": "Vow hump configuration", - "url": "https://github.com/sky-ecosystem/dss/blob/master/src/vow.sol#L96-L103", - "type": "github" - }, - { - "name": "Vow contract β€” read hump", - "url": "https://etherscan.io/address/0xA950524441892A31ebddF91d3cEEFa04Bf454466#readContract", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "tags": ["Reference"], - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "No verified offchain revenue streams flow to SKY holders. The DAI Foundation is an independent non-profit that does not distribute profits.", - "evidence": [ - { - "name": "Foundation Independence", - "summary": "The DAI Foundation operates independently under Danish law. It holds trademarks but does not distribute value to tokenholders.", - "urls": [ - { - "name": "Foundation Mandate", - "url": "https://daifoundation.org/mandate", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "All code is open source (AGPL-3.0) and verified on Etherscan. Anyone can audit the contracts.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "SKY token source code is open source and verified on Etherscan. Anyone can audit the code.", - "evidence": [ - { - "name": "Verified Source", - "summary": "Source code matches deployed bytecode. Licensed under AGPL-3.0.", - "urls": [ - { - "name": "Source Code", - "url": "https://github.com/sky-ecosystem/sky/blob/master/src/Sky.sol", - "type": "github" - }, - { - "name": "Verified on Etherscan", - "url": "https://etherscan.io/address/0x56072C95FAA701256059aa122697B133aDEd9279#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "Every protocol contract is open source. A \"chainlog\" provides a registry of all deployed contract addresses.", - "evidence": [ - { - "name": "Open Source Repositories", - "summary": "All protocol code is open source under AGPL-3.0. Core contracts, governance, and stablecoins all have public repositories.", - "urls": [ - { - "name": "Core Protocol", - "url": "https://github.com/sky-ecosystem/dss", - "type": "github" - }, - { - "name": "Contract Registry", - "url": "https://github.com/sky-ecosystem/spells-mainnet/blob/master/src/test/addresses_mainnet.sol", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "Aragon has not been able to verify the concentration of holdings.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "unevaluated", - "notes": "Aragon has not been able to verify the concentration of holdings.", - "evidence": [] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "unevaluated", - "notes": "Vesting contracts exist but specific unlock schedules were not enumerated in this analysis.", - "evidence": [ - { - "name": "Vesting Contracts", - "summary": "Vesting contracts exist for token distributions. Governance controls these schedules.", - "urls": [ - { - "name": "Vest Contract", - "url": "https://etherscan.io/address/0x67eaDb3288cceDe034cE95b0511DCc65cf630bB6#code", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Current Sky branding is not controlled by SKY tokenholders. Skybase International's terms reserve trademarks, service marks, logos, and trade names associated with the Services, including the Sky name, to the service operator or its licensors, and the DAI Foundation independently holds the legacy Maker and DAI trademarks. All code is open source (AGPL-3.0).", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Official Skybase International terms state that trademarks, service marks, logos, and trade names associated with the Services are proprietary to Skybase International or its licensors, and explicitly include the Sky name. Aragon has not found evidence that current Sky / USDS branding is owned by a tokenholder-controlled legal entity. Separately, the DAI Foundation holds the legacy Maker and DAI trademarks outside token governance.", - "evidence": [ - { - "name": "Skybase International Terms of Use", - "summary": "Skybase International's terms say trademarks, service marks, logos, and trade names associated with the Services are proprietary to Skybase International or its licensors, and explicitly include the Sky name.", - "urls": [ - { - "name": "Terms of Use", - "url": "https://docs.sky.money/legal/skybase-international/terms-of-use", - "type": "docs" - } - ] - }, - { - "name": "DAI Foundation", - "summary": "Holds the legacy Maker and DAI trademarks. Operates under Danish law, independent of token governance.", - "urls": [ - { - "name": "Foundation Website", - "url": "https://daifoundation.org", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "unevaluated", - "notes": "Domain ownership not verified. Open source frontends exist that anyone can deploy.", - "evidence": [ - { - "name": "Open Source Frontends", - "summary": "Multiple open source frontends exist. Anyone can deploy their own interface.", - "urls": [ - { - "name": "Governance Portal Source", - "url": "https://github.com/sky-ecosystem/governance-portal-v2", - "type": "github" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "positive", - "notes": "All code is AGPL-3.0 licensed. Anyone can fork and deploy the protocol. The license requires source disclosure.", - "evidence": [ - { - "name": "AGPL-3.0 License", - "summary": "Copyleft license. Anyone can fork and deploy, but must share source code of modifications.", - "urls": [ - { - "name": "License File", - "url": "https://github.com/sky-ecosystem/sky/blob/master/LICENSE", - "type": "github" - } - ] - } - ] - } - ] - } - ], - "ethfi": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "ETHFI tokenholders do not have binding onchain control. Governance uses offchain voting with multisig execution.\n\nThe protocol uses a two-timelock system for upgrades and operations. A multisig controls the Upgrade Timelock, which owns the RoleRegistry and authorizes protocol upgrades.\n\nThe mainnet token is not upgradeable. L2 tokens on Arbitrum and Base are upgradeable by multisigs with no timelock.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "at_risk", - "notes": "No onchain Governor contract deployed. Governance uses offchain voting with a 4-day voting period and 1M ETHFI quorum. Execution is handled by multisig, meaning tokenholders can signal preference but cannot force execution.\n\nThe protocol has published a multi-stage decentralisation roadmap and is currently in Phase 0, which focuses on launching the token and establishing the initial voter base. Phase 1 targets full Governor deployment with treasury access.", - "evidence": [ - { - "name": "Governance Structure", - "summary": "Phase 0 focuses on launching the token and establishing the initial voter base. Phase 1 targets full Governor deployment with treasury access.", - "urls": [ - { - "name": "Governance Info", - "url": "https://vote.ether.fi/info", - "type": "docs" - }, - { - "name": "Governance Resources", - "url": "https://governance.ether.fi/t/ether-fi-governance-official-resources/2140", - "type": "docs" - }, - { - "name": "Governance Roadmap", - "url": "https://etherfi.gitbook.io/gov/governance-roadmap", - "type": "docs" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "warning", - "notes": "The protocol uses a two-timelock system. The Upgrade Timelock owns the RoleRegistry and controls protocol upgrades. The Operating Timelock handles day-to-day operations.\n\nTokenholders do not elect or control the multisig signers. Team-controlled multisigs propose all timelock operations.", - "evidence": [ - { - "name": "RoleRegistry Owner (Upgrade Timelock)", - "summary": "The RoleRegistry is owned by the Upgrade Timelock (72h delay). Role changes require timelock approval.", - "urls": [ - { - "name": "RoleRegistry Contract", - "url": "https://etherscan.io/address/0x62247D29B4B9BECf4BB73E0c722cf6445cfC7cE9", - "type": "explorer" - }, - { - "name": "Upgrade Timelock (72h)", - "url": "https://etherscan.io/address/0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761", - "type": "explorer" - } - ] - }, - { - "name": "Two-Timelock System", - "summary": "Upgrade Timelock (72h delay, 4-of-7 proposer) for upgrades. Operating Timelock (8h delay, 3-of-5 proposer) for routine operations.", - "urls": [ - { - "name": "Upgrade Admin (4-of-7)", - "url": "https://etherscan.io/address/0xcdd57d11476c22d265722f68390b036f3da48c21", - "type": "explorer" - }, - { - "name": "Operating Timelock (8h)", - "url": "https://etherscan.io/address/0xcD425f44758a08BaAB3C4908f3e3dE5776e45d7a", - "type": "explorer" - }, - { - "name": "Operating Admin (3-of-5)", - "url": "https://etherscan.io/address/0x2aCA71020De61bb532008049e1Bd41E451AE8AdC", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "warning", - "notes": "Core protocol contracts (LiquidityPool, eETH, weETH, EtherFiAdmin) are owned by the Upgrade Timelock, which enforces a delay before upgrades execute.\n\nBoring Vaults (sETHFI, eUSD, weETHs, weETHk) use a different pattern: they are non-upgradeable but controlled via RolesAuthority contracts owned by multisigs. L2 ETHFI tokens can be upgraded instantly by multisigs with no timelock.", - "evidence": [ - { - "name": "Upgrade Path", - "summary": "Core contracts are owned by the Upgrade Timelock (72h delay). Boring Vaults are non-upgradeable but controlled via RolesAuthority.", - "urls": [ - { - "name": "RoleRegistry Upgrade Check", - "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/RoleRegistry.sol#L76-L78", - "type": "github" - }, - { - "name": "LiquidityPool Upgrade Authorization", - "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/LiquidityPool.sol#L529-L531", - "type": "github" - } - ] - }, - { - "name": "RoleRegistry Owner", - "summary": "The RoleRegistry is owned by the Upgrade Timelock. Core contract upgrades require a 72-hour delay.", - "urls": [ - { - "name": "RoleRegistry Contract", - "url": "https://etherscan.io/address/0x62247D29B4B9BECf4BB73E0c722cf6445cfC7cE9", - "type": "explorer" - }, - { - "name": "Upgrade Timelock", - "url": "https://etherscan.io/address/0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "warning", - "notes": "The Ethereum mainnet ETHFI token is not upgradeable. L2 ETHFI tokens on Arbitrum and Base are upgradeable by multisigs with no timelock protection.\n\nL2 token holders face higher risk due to the ability to instantly upgrade the token contract.", - "evidence": [ - { - "name": "Mainnet Token (Not Upgradeable)", - "summary": "The Ethereum mainnet ETHFI token is not upgradeable. No EIP-1967 implementation slot exists.", - "urls": [ - { - "name": "ETHFI Token Contract", - "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", - "type": "explorer" - } - ] - }, - { - "name": "L2 Tokens (Upgradeable, No Timelock)", - "summary": "L2 ETHFI tokens are upgradeable proxies owned by 3-of-6 multisigs. No timelock protects L2 upgrades.", - "urls": [ - { - "name": "Arbitrum ETHFI", - "url": "https://arbiscan.io/address/0x7189fb5B6504bbfF6a852B13B7B82a3c118fDc27", - "type": "explorer" - }, - { - "name": "Arbitrum Admin (3-of-6)", - "url": "https://arbiscan.io/address/0x0c6ca434756eedf928a55ebeaf0019364b279732", - "type": "explorer" - }, - { - "name": "Base ETHFI", - "url": "https://basescan.org/address/0x6C240DDA6b5c336DF09A4D011139beAAa1eA2Aa2", - "type": "explorer" - }, - { - "name": "Base Admin (3-of-6)", - "url": "https://basescan.org/address/0x7a00657a45420044bc526b90ad667affaee0a868", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "positive", - "notes": "ETHFI has a fixed supply of 1 billion tokens with no mint function. All tokens were minted at deployment. Supply can only decrease through the ERC20Burnable function. Approximately 1.46M tokens have been burned.", - "evidence": [ - { - "name": "Fixed Supply", - "summary": "No mint function exists in the contract. Holders can burn their own tokens via ERC20Burnable.", - "urls": [ - { - "name": "ETHFI Allocations", - "url": "https://etherfi.gitbook.io/gov/ethfi-allocations", - "type": "docs" - }, - { - "name": "Token Contract", - "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "warning", - "notes": "Protocol contracts can be paused by addresses holding the PROTOCOL_PAUSER role, which is assigned via the RoleRegistry (owned by Upgrade Timelock). The ETHFI token itself has no pause function.\n\neETH holders could be temporarily blocked from withdrawing to ETH if LiquidityPool is paused.", - "evidence": [ - { - "name": "Pause Authority", - "summary": "The EtherFiAdmin contract can pause protocol operations including the oracle, staking manager, auction manager, nodes manager, liquidity pool, and membership manager.", - "urls": [ - { - "name": "EtherFiAdmin Pause Function", - "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/EtherFiAdmin.sol#L102-L127", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "warning", - "notes": "The mainnet ETHFI token contains no blacklist, freeze, or transfer restriction mechanisms. L2 ETHFI tokens are upgradeable by multisigs with no timelock, which could allow introducing censorship functions through an upgrade.\n\nL1 token has no censorship risk. L2 tokens have potential censorship risk due to instant upgrade capability.", - "evidence": [ - { - "name": "Token Analysis", - "summary": "L1 token has no censorship capabilities. L2 tokens are upgradeable, creating a potential censorship vector on Arbitrum and Base.", - "urls": [ - { - "name": "ETHFI Token Contract", - "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", - "type": "explorer" - }, - { - "name": "Arbitrum ETHFI (Upgradeable)", - "url": "https://arbiscan.io/address/0x7189fb5B6504bbfF6a852B13B7B82a3c118fDc27", - "type": "explorer" - }, - { - "name": "Base ETHFI (Upgradeable)", - "url": "https://basescan.org/address/0x6C240DDA6b5c336DF09A4D011139beAAa1eA2Aa2", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "An active buyback program distributes purchased ETHFI to sETHFI holders. eETH withdrawal fees fund buybacks, while any additional funding from broader protocol revenue is currently Foundation-discretionary. The Treasury is a multisig controlled by the team.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "positive", - "notes": "An ETHFI buyback program is operational, distributing purchased tokens to sETHFI holders. Buybacks are funded by eETH withdrawal fees weekly, and any additional contribution from broader protocol revenue is currently Foundation-discretionary.\n\nBuyback execution is controlled by a multisig where any single signer can execute. Distribution is announced via Foundation communications, not enforced by smart contract.", - "evidence": [ - { - "name": "Buyback Program", - "summary": "Buybacks documented in governance materials. The buyback wallet is a 1-of-5 multisig (any single signer can execute).", - "urls": [ - { - "name": "ETHFI Buyback Program", - "url": "https://etherfi.gitbook.io/gov/ethfi-buyback-program", - "type": "docs" - }, - { - "name": "Buyback History (Dune)", - "url": "https://dune.com/ether_fi/ethfi-buybacks-and-protocol-revenue-sources", - "type": "docs" - }, - { - "name": "sETHFI Staking", - "url": "https://www.ether.fi/app/ethfi", - "type": "docs" - }, - { - "name": "Buyback Wallet (1-of-5)", - "url": "https://etherscan.io/address/0x2f5301a3D59388c509C65f8698f521377D41Fd0F", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "warning", - "notes": "The primary Treasury is a multisig controlled by the team. The ETHFI Allocations documentation mentions multiple Safes under 'Treasury' β€” this analysis verified the main Treasury contract. Tokenholders have no direct control over treasury assets.", - "evidence": [ - { - "name": "Treasury Control", - "summary": "The verified Treasury is a 3-of-8 Gnosis Safe. Additional Treasury addresses may exist per ETHFI Allocations documentation.", - "urls": [ - { - "name": "Treasury Contract", - "url": "https://etherscan.io/address/0x0c83EAe1FE72c390A02E426572854931EefF93BA", - "type": "explorer" - }, - { - "name": "Upgrade Timelock", - "url": "https://etherscan.io/address/0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether only tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "warning", - "notes": "The percentage of protocol revenue allocated to buybacks is stated in documentation only β€” it is not hardcoded in any contract or set by an on-chain parameter. The Foundation has full discretion over actual buyback amounts and timing.\n\nFee recipients are set by admin roles via the RoleRegistry. Tokenholders cannot modify the fee structure through binding governance.", - "evidence": [ - { - "name": "Fee Configuration", - "summary": "Buyback percentages are stated in docs only, not enforced on-chain. Fee parameters are controlled by admin roles.", - "urls": [ - { - "name": "LiquidityPool Fee Functions", - "url": "https://github.com/etherfi-protocol/smart-contracts/blob/master/src/LiquidityPool.sol#L434-L439", - "type": "github" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "tags": ["Reference"], - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "unevaluated", - "notes": "Aragon developers have not verified additional offchain value accrual mechanisms. No legally binding revenue sharing arrangements or licensing revenue documented.", - "evidence": [] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "The ETHFI token contract is verified on Etherscan but not published to a public GitHub repository. All core protocol contracts are verified and open source under MIT license with multiple security audits and formal verification through Certora.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "warning", - "notes": "The ETHFI token contract is verified on Etherscan but is not published to a public GitHub repository. Protocol contracts are public, but the token contract is Etherscan-only.", - "evidence": [ - { - "name": "Token Verification", - "summary": "Token source verified on Etherscan. Compiler: Solidity 0.8.20, License: MIT.", - "urls": [ - { - "name": "Etherscan Verified Source", - "url": "https://etherscan.io/address/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "All core protocol contracts are verified on Etherscan and match the public GitHub repository. The code is MIT licensed with formal verification via Certora and multiple audits available.", - "evidence": [ - { - "name": "Protocol Source & Audits", - "urls": [ - { - "name": "etherfi-protocol/smart-contracts", - "url": "https://github.com/etherfi-protocol/smart-contracts", - "type": "github" - }, - { - "name": "Audit Reports", - "url": "https://github.com/etherfi-protocol/smart-contracts/tree/master/audits", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "Over 55% of tokens are allocated to Investors (33.74%) and Core Contributors (21.47%), subject to transparent vesting schedules published in official documentation. Vesting completion is expected by end of 2030.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "warning", - "notes": "Token allocation includes Investors at 33.74% (2-year vest, 1-year cliff), Treasury at 21.62%, Core Contributors at 21.47% (3-year vest, 1-year cliff), User Airdrops at 19.27%, and Partnerships at 3.9%. Vesting mitigates immediate concentration, and schedules are transparently documented.", - "evidence": [ - { - "name": "Token Allocation", - "urls": [ - { - "name": "ETHFI Allocations", - "url": "https://etherfi.gitbook.io/gov/ethfi-allocations", - "type": "docs" - } - ] - } - ] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "warning", - "notes": "Continuous unlocks from team and investor allocations occur according to published vesting schedules. Core Contributors have 3-year vesting with a 1-year cliff, while Investors have 2-year vesting with a 1-year cliff. Full vesting completion is expected by end of 2030.", - "evidence": [ - { - "name": "Vesting Schedule", - "urls": [ - { - "name": "ETHFI Allocations", - "url": "https://etherfi.gitbook.io/gov/ethfi-allocations", - "type": "docs" - }, - { - "name": "DefiLlama Unlocks", - "url": "https://defillama.com/unlocks/ether.fi", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": ["Reference"], - "about": "Economically material assets and obligations often sit offchainβ€”outside the custody and direct enforcement of a tokenβ€”while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Trademarks are owned by Ether.Fi SEZC (Cayman Islands company), not a tokenholder-controlled entity. The ether.fi domain and platform are operated by this company. Protocol smart contracts are MIT licensed, but non-contract IP has restricted licensing.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Trademarks are owned by Ether.Fi SEZC (Cayman Islands company), not a tokenholder-controlled entity. The Terms of Use state that company names, logos, and related designs are trademarks of the Company or its affiliates.", - "evidence": [ - { - "name": "Trademark Ownership", - "urls": [ - { - "name": "Terms of Use", - "url": "https://etherfi.gitbook.io/etherfi/ether.fi-legal/terms-of-use", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "The ether.fi domain and platform are operated by Ether.Fi SEZC, a Cayman Islands Special Economic Zone Company. There is no documented relationship between the company and DAO, and the company operates with unilateral control over terms and services.", - "evidence": [ - { - "name": "Legal Entity", - "urls": [ - { - "name": "Terms of Use", - "url": "https://etherfi.gitbook.io/etherfi/ether.fi-legal/terms-of-use", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Smart contracts are MIT licensed, allowing unrestricted use and modification. However, non-contract IP including website content, documentation, and brand assets is restricted. Users receive only a non-transferable, non-sublicensable, non-exclusive, revocable license for personal use.", - "evidence": [ - { - "name": "License", - "urls": [ - { - "name": "GitHub Repository", - "url": "https://github.com/etherfi-protocol/smart-contracts", - "type": "github" - }, - { - "name": "Terms of Use (Non-Contract IP)", - "url": "https://etherfi.gitbook.io/etherfi/ether.fi-legal/terms-of-use", - "type": "docs" - } - ] - } - ] - } - ] - } - ], - "ondo": [ - { - "id": "onchain-ctrl", - "name": "Onchain Control", - "tags": ["Metric 1"], - "about": "This metric evaluates whether economically material outcomes are mediated through tokenholder governance, or whether admins, multisigs, security councils, or other privileged roles retain onchain powers that can change the rules or selectively restrict use and exit.", - "summary": "Core Ondo products are controlled by team multisigs. ONDO governance controls Flux Finance, but Flux appears small relative to Ondo's other product TVL. The ONDO token contract has team-held admin and minting roles. Supply is 10B with active mint capability.", - "criteria": [ - { - "id": "onchain-ctrl__governance-workflow", - "name": "Onchain Governance Workflow", - "about": "Evaluates whether an onchain process exists that grants tokenholders ultimate authority over protocol decisions.", - "status": "at_risk", - "notes": "ONDO tokenholders have no governance control over Ondo's core products (OUSG, USDY, Global Markets). These products represent ~$3.5B TVL (98.8% of total) and are controlled by company multisigs.", - "evidence": [ - { - "name": "OUSG/USDY/Global Markets Governance (Company-Controlled)", - "summary": "All core products are controlled by company multisigs with no tokenholder involvement:\n\n1. OUSG: Management Multisig holds DEFAULT_ADMIN_ROLE and ProxyAdmin ownership.\n\n2. USDY: Team Multisig holds ProxyAdmin ownership.\n\n3. Global Markets: TimelockController with 2-hour delay, controlled by company multisigs.\n\nNo forum discussion required. No onchain tokenholder vote. Changes proposed and executed by multisig signers.", - "urls": [ - { - "name": "OUSG", - "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#code", - "type": "explorer" - }, - { - "name": "USDY", - "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#code", - "type": "explorer" - }, - { - "name": "GMTokenManager", - "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c#code", - "type": "explorer" - }, - { - "name": "Management Multisig (OUSG admin)", - "url": "https://etherscan.io/address/0xAEd4caF2E535D964165B4392342F71bac77e8367", - "type": "explorer" - }, - { - "name": "USDY ProxyAdmin owner", - "url": "https://etherscan.io/address/0x1a694A09494E214a3Be3652e4B343B7B81A73ad7", - "type": "explorer" - }, - { - "name": "Global Markets TimelockController", - "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__role-accountability", - "name": "Role Accountability", - "about": "Determines whether all privileged or value-impacting roles are governed, revocable, and accountable to tokenholders.", - "status": "at_risk", - "notes": "OUSG, USDY, and Global Markets are entirely controlled by company multisigs. The ONDO token itself has DEFAULT_ADMIN_ROLE and MINTER_ROLE held by team multisigs, not the DAO.", - "evidence": [ - { - "name": "Team-Controlled Roles (ONDO Token)", - "summary": "DEFAULT_ADMIN_ROLE: Team Multisig (4-of-7).\n\nMINTER_ROLE: Team Multisig (4-of-7).\n\nThe team can grant/revoke roles and mint new ONDO tokens without DAO approval.", - "urls": [ - { - "name": "Team Multisig", - "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", - "type": "explorer" - }, - { - "name": "ONDO Token", - "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", - "type": "explorer" - } - ] - }, - { - "name": "Team-Controlled Roles (OUSG/USDY)", - "summary": "OUSG DEFAULT_ADMIN_ROLE: Management Multisig (4-of-7).\n\nOUSG ProxyAdmin owner: Management Multisig (4-of-7).\n\nrOUSG ProxyAdmin owner: Management Multisig (4-of-7).\n\nUSDY ProxyAdmin owner: Team Multisig (4-of-7).\n\nrUSDY ProxyAdmin owner: Team Multisig (4-of-7).", - "urls": [ - { - "name": "OUSG", - "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92", - "type": "explorer" - }, - { - "name": "Management Multisig (4-of-7)", - "url": "https://etherscan.io/address/0xAEd4caF2E535D964165B4392342F71bac77e8367", - "type": "explorer" - }, - { - "name": "rOUSG", - "url": "https://etherscan.io/address/0x54043c656F0FAd0652D9Ae2603cDF347c5578d00", - "type": "explorer" - }, - { - "name": "USDY", - "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C", - "type": "explorer" - }, - { - "name": "Team Multisig (4-of-7)", - "url": "https://etherscan.io/address/0x1a694A09494E214a3Be3652e4B343B7B81A73ad7", - "type": "explorer" - }, - { - "name": "rUSDY", - "url": "https://etherscan.io/address/0xaf37c1167910ebC994e266949387d2c7C326b879", - "type": "explorer" - } - ] - }, - { - "name": "Team-Controlled Roles (Global Markets)", - "summary": "GMTokenManager/USDon DEFAULT_ADMIN_ROLE: TimelockController (2-hour delay).\n\nTimelockController PROPOSER_ROLE: Multisig (4-of-7).\n\nTimelockController EXECUTOR_ROLE: Multisig (1-of-8) + EOA.\n\nTimelockController DEFAULT_ADMIN_ROLE: Multisig (5-of-9).", - "urls": [ - { - "name": "GMTokenManager", - "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c#readContract", - "type": "explorer" - }, - { - "name": "Global Markets TimelockController", - "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", - "type": "explorer" - }, - { - "name": "PROPOSER_ROLE holder (Multisig 4-of-7)", - "url": "https://etherscan.io/address/0x71A4d411b5f7941Dee020417fca30413712f1646", - "type": "explorer" - }, - { - "name": "EXECUTOR_ROLE holder (Multisig 1-of-8)", - "url": "https://etherscan.io/address/0x2e55b738F5969Eea10fB67e326BEE5e2fA15A2CC", - "type": "explorer" - }, - { - "name": "EXECUTOR_ROLE holder (EOA)", - "url": "https://etherscan.io/address/0xfF1621Ee754512B34a6Bd62A941Cc4d5E4d0b85B", - "type": "explorer" - }, - { - "name": "DEFAULT_ADMIN_ROLE holder (Multisig 5-of-9)", - "url": "https://etherscan.io/address/0xcD35671dCAb88d05EE29dC4D360181529390B17f", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__protocol-upgrade", - "name": "Protocol Upgrade Authority", - "about": "Determines whether core protocol logic can be upgraded and whether such upgrades are controlled by tokenholders.", - "status": "at_risk", - "notes": "OUSG, USDY, and Global Markets are upgradeable contracts controlled by team multisigs on all chains. ONDO tokenholders have no upgrade control over any core products.", - "evidence": [ - { - "name": "OUSG/USDY (Team-Controlled)", - "summary": "Ethereum OUSG ProxyAdmin owner: Management Multisig (4-of-7).\n\nEthereum USDY ProxyAdmin owner: Team Multisig (4-of-7).\n\nPolygon OUSG ProxyAdmin owner: 3-of-6 Multisig.\n\nMantle USDY ProxyAdmin owner: 4-of-7 Multisig.\n\nArbitrum USDY ProxyAdmin owner: 4-of-7 Multisig.\n\nThe DAO has no upgrade authority over OUSG or USDY on any chain.", - "urls": [ - { - "name": "OUSG Proxy", - "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#readProxyContract", - "type": "explorer" - }, - { - "name": "OUSG ProxyAdmin Owner (Ethereum)", - "url": "https://etherscan.io/address/0xAEd4caF2E535D964165B4392342F71bac77e8367", - "type": "explorer" - }, - { - "name": "USDY Proxy", - "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#readProxyContract", - "type": "explorer" - }, - { - "name": "USDY ProxyAdmin Owner (Ethereum)", - "url": "https://etherscan.io/address/0x1a694A09494E214a3Be3652e4B343B7B81A73ad7", - "type": "explorer" - }, - { - "name": "Polygon OUSG ProxyAdmin Owner", - "url": "https://polygonscan.com/address/0x4413073440A568790c1b2b06B47F7D0a443574d0", - "type": "explorer" - }, - { - "name": "Mantle USDY ProxyAdmin Owner", - "url": "https://mantlescan.xyz/address/0xC8A7870fFe41054612F7f3433E173D8b5bFcA8E3", - "type": "explorer" - }, - { - "name": "Arbitrum USDY ProxyAdmin Owner", - "url": "https://arbiscan.io/address/0xC4ac5c2fA461901b4D91832d03A7018092eDCb4D", - "type": "explorer" - } - ] - }, - { - "name": "Global Markets (Team-Controlled via TimelockController)", - "summary": "USDon is an upgradeable proxy. Upgrade authority resides with TimelockController (2-hour delay) which is controlled by company multisigs. ONDO tokenholders have no upgrade control.", - "urls": [ - { - "name": "USDon", - "url": "https://etherscan.io/address/0xAcE8E719899F6E91831B18AE746C9A965c2119F1#code", - "type": "explorer" - }, - { - "name": "Global Markets TimelockController", - "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__token-upgrade", - "name": "Token Upgrade Authority", - "about": "Assesses whether token behavior can be modified and, if so, whether such changes are controlled by tokenholder governance.", - "status": "positive", - "notes": "The ONDO token is not upgradeable (no proxy pattern). It uses AccessControl with DEFAULT_ADMIN_ROLE and MINTER_ROLE held by a team multisig, not the DAO.", - "evidence": [ - { - "name": "ONDO Token Roles", - "summary": "DEFAULT_ADMIN_ROLE holder: Team Multisig.\nMINTER_ROLE holder: Team Multisig.\n\nThe team can grant/revoke roles and mint new tokens. The DAO has no control over these roles.", - "urls": [ - { - "name": "ONDO Token", - "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", - "type": "explorer" - }, - { - "name": "Team Multisig", - "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", - "type": "explorer" - } - ] - }, - { - "name": "Contract Source Note", - "summary": "The deployed ONDO token uses AccessControl. The public ondo-v1 repository shows a simpler Ownable-based contract, indicating the deployed contract differs from the public repo.", - "urls": [ - { - "name": "Public ondo-v1 repo (differs from deployed)", - "url": "https://github.com/ondoprotocol/ondo-v1/blob/main/contracts/tokens/Ondo.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__supply", - "name": "Supply Control", - "about": "Evaluates whether token supply changes are programmatic or subject exclusively to tokenholder-approved governance processes.", - "status": "at_risk", - "notes": "ONDO has a total supply of 10B tokens. MINTER_ROLE exists and is held by a team multisig. The supply is not immutably fixed.", - "evidence": [ - { - "name": "Current Supply", - "summary": "Total supply: 10,000,000,000 ONDO (verified onchain).\nTeam Multisig balance: ~5.9B ONDO (~59% of supply).\n\nDocumentation states \"no scheduled or planned inflation\" but this is a policy statement, not code enforcement.", - "urls": [ - { - "name": "ONDO Token totalSupply", - "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", - "type": "explorer" - }, - { - "name": "Token Documentation", - "url": "https://docs.ondo.foundation/ondo-token", - "type": "docs" - } - ] - }, - { - "name": "Mint Function Exists", - "summary": "The deployed ONDO token includes a `mint()` function restricted to MINTER_ROLE holders. Team multisig holds MINTER_ROLE and can mint new tokens without DAO approval.", - "urls": [ - { - "name": "ONDO Token (verified source)", - "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#code", - "type": "explorer" - }, - { - "name": "Team Multisig (MINTER_ROLE holder)", - "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__access-gating", - "name": "Privileged Access Gating", - "about": "Assesses whether any bounded actor set can block or selectively restrict economically meaningful protocol actions or exit paths, versus access that is permissionless and symmetric for similarly situated users.", - "status": "at_risk", - "notes": "OUSG and USDY have extensive transfer restrictions (KYC requirements, blocklists, sanctions checks) enforced by Ondo Finance, not the DAO. The company controls who can hold these products.", - "evidence": [ - { - "name": "OUSG Transfer Restrictions", - "summary": "KYC required via KYCRegistry. The `_beforeTokenTransfer` hook enforces three-way checks: sender, receiver, and msg.sender must all be KYC-approved. The DAO does not control the KYC registry.", - "urls": [ - { - "name": "OUSG Contract (KYC enforcement)", - "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#code", - "type": "explorer" - }, - { - "name": "OUSG KYC check (source)", - "url": "https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousg.sol#L62-L89", - "type": "github" - }, - { - "name": "KYCRegistry", - "url": "https://etherscan.io/address/0x56A5D911052323D688C731d516530878557463e7", - "type": "explorer" - } - ] - }, - { - "name": "USDY Transfer Restrictions", - "summary": "USDY has blocklist, allowlist, and sanctions list checks. Transfers require passing all three checks. These are controlled by the company, not the DAO.", - "urls": [ - { - "name": "USDY restrictions (source)", - "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/usdy/USDY.sol#L84-L115", - "type": "github" - }, - { - "name": "USDY Contract", - "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#code", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "onchain-ctrl__censorship", - "name": "Token Censorship", - "about": "Examines whether any roles exist that can freeze, blacklist, seize, or otherwise censor token balances or transfers.", - "status": "positive", - "notes": "The ONDO token has no blocklist, freeze, or seizure functions. Transfers are permissionless. Transfers were enabled in January 2024.", - "evidence": [ - { - "name": "No Censorship Capability", - "summary": "`transferAllowed = true` (verified onchain, enabled January 2024).\nNo blacklist mapping.\nNo pause function for transfers.\nNo force transfer or seize functions.\n\nThe token contract has a `whenTransferAllowed` modifier but transfers are permanently enabled.", - "urls": [ - { - "name": "ONDO Token", - "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", - "type": "explorer" - }, - { - "name": "Source code (ondo-v1)", - "url": "https://github.com/ondoprotocol/ondo-v1/blob/main/contracts/tokens/Ondo.sol", - "type": "github" - } - ] - } - ] - } - ] - }, - { - "id": "val-accrual", - "name": "Value Accrual", - "tags": ["Metric 2"], - "about": "This metric evaluates whether the system's operation produces observable, onchain value flows (or token-scarcity effects) that accrue value to tokenholders under rule-based constraints that do not rely on manual transfers, trusted executors, or other unenforced expectations.", - "summary": "No active value accrual mechanism was identified for ONDO holders. Ondo has multiple products with documented product-level economics, including yield, fees, expenses, and spreads, but no identified flow from those economics to ONDO holders or an ONDO-controlled treasury. Flux is ONDO-governed but small relative to Ondo's other product TVL, so it does not materially change the assessment.", - "criteria": [ - { - "id": "val-accrual__active", - "name": "Accrual Active", - "about": "Assesses whether value flows to tokenholders are currently active rather than merely theoretical or proposed.", - "status": "at_risk", - "notes": "No active value accrual mechanism for ONDO tokenholders was identified. Ondo products have documented product-level economics, but Aragon did not identify a fee distributor, staking reward, buyback program, DAO treasury, or other mechanism routing product economics to ONDO holders.", - "evidence": [ - { - "name": "Product Economics vs ONDO Holder Accrual", - "summary": "Ondo has products with documented product-level economics, including yield, fees, expenses, and spreads. Those mechanics do not identify an ONDO-holder accrual mechanism.", - "urls": [ - { - "name": "OUSG Fees", - "url": "https://docs.ondo.finance/qualified-access-products/ousg/fees-and-taxes", - "type": "docs" - }, - { - "name": "OUSG Yield", - "url": "https://docs.ondo.finance/qualified-access-products/ousg/yield", - "type": "docs" - }, - { - "name": "USDY Product Economics", - "url": "https://docs.ondo.finance/general-access-products/usdy/comparison-stablecoins", - "type": "docs" - }, - { - "name": "Global Markets Fees", - "url": "https://docs.ondo.finance/ondo-global-markets/fees-and-taxes", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__treasury", - "name": "Treasury Ownership", - "about": "Determines whether protocol treasury assets are programmatically controlled by tokenholder governance.", - "status": "at_risk", - "notes": "Aragon has not been able to identify a DAO treasury address for ONDO governance. No FeeDistributor or Treasury contract exists.", - "evidence": [ - { - "name": "No Treasury Identified", - "summary": "No DAO treasury contract identified. No fee distribution mechanism was identified for Ondo product economics. No governance proposals for treasury creation were identified.", - "urls": [ - { - "name": "Ondo DAO Proposals", - "url": "https://www.tally.xyz/gov/ondo-dao", - "type": "docs" - } - ] - } - ] - }, - { - "id": "val-accrual__mechanism", - "name": "Accrual Mechanism Control", - "about": "Evaluates whether tokenholders can modify parameters governing value capture, such as fees or revenue routing.", - "status": "at_risk", - "notes": "ONDO holders have no identified control over revenue routing for Ondo's core products. Core product parameters are controlled by company-held roles and multisigs. Flux is ONDO-governed but represents a small share of Ondo TVL.", - "evidence": [ - { - "name": "OUSG/USDY Price Oracles (Company-Controlled)", - "summary": "The price oracles that determine OUSG/USDY NAV are controlled by SETTER_ROLE-restricted `setPrice()` functions. ONDO tokenholders cannot control these oracle price-update roles.", - "urls": [ - { - "name": "OUSG Oracle", - "url": "https://etherscan.io/address/0x0502c5ae08E7CD64fe1AEDA7D6e229413eCC6abe", - "type": "explorer" - }, - { - "name": "USDY Oracle", - "url": "https://etherscan.io/address/0xA0219AA5B31e65Bc920B5b6DFb8EdF0988121De0", - "type": "explorer" - }, - { - "name": "RWAOracleExternalComparisonCheck.sol (setPrice)", - "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/rwaOracles/RWAOracleExternalComparisonCheck.sol#L147", - "type": "github" - }, - { - "name": "RWAOracleRateCheck.sol (setPrice rate limit)", - "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/rwaOracles/RWAOracleRateCheck.sol#L81-L90", - "type": "github" - } - ] - }, - { - "name": "Global Markets (Company-Controlled via TimelockController)", - "summary": "GMTokenManager admin is TimelockController (2-hour delay). ONDO tokenholders have no control over Global Markets fee parameters.", - "urls": [ - { - "name": "GMTokenManager", - "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c", - "type": "explorer" - }, - { - "name": "Global Markets TimelockController", - "url": "https://etherscan.io/address/0x3715B2154d2FF4C5B027C7a1f734B53F27bc34f1", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "val-accrual__offchain", - "name": "Offchain Value Accrual", - "tags": [ - "Reference" - ], - "about": "Are there additional offchain value accrual flows that benefit tokenholders?", - "status": "warning", - "notes": "Ondo product economics may accrue through product-holder yield, issuer/operator fees, spreads, expenses, or service revenue, but no documented offchain value-accrual flow to ONDO holders was identified.", - "evidence": [ - { - "name": "Ondo Product Economics", - "summary": "Ondo products have documented product-level economics, including yield, fees, expenses, and spreads. These mechanics describe product economics, not ONDO-holder accrual. No onchain mechanism guarantees that residual issuer/operator economics flow to ONDO holders.", - "urls": [ - { - "name": "OUSG Fees", - "url": "https://docs.ondo.finance/qualified-access-products/ousg/fees-and-taxes", - "type": "docs" - }, - { - "name": "OUSG Yield", - "url": "https://docs.ondo.finance/qualified-access-products/ousg/yield", - "type": "docs" - }, - { - "name": "USDY Product Economics", - "url": "https://docs.ondo.finance/general-access-products/usdy/comparison-stablecoins", - "type": "docs" - }, - { - "name": "USDY Issuer / Ondo Finance Relationship", - "url": "https://docs.ondo.finance/general-access-products/usdy/important-notes", - "type": "docs" - }, - { - "name": "Global Markets Fees", - "url": "https://docs.ondo.finance/ondo-global-markets/fees-and-taxes", - "type": "docs" - } - ] - }, - { - "name": "Global Markets Revenue", - "summary": "Global Markets documentation describes fees and quote spreads retained by Ondo Global Markets. Aragon did not identify a mechanism routing those fees or spreads to ONDO holders or an ONDO-controlled treasury.", - "urls": [ - { - "name": "Global Markets Fees", - "url": "https://docs.ondo.finance/ondo-global-markets/fees-and-taxes", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "verifiability", - "name": "Verifiability", - "tags": ["Metric 3"], - "about": "This metric measures whether a token's economically material code and deployments are independently verifiable from primary evidence without relying on insider assurances.", - "summary": "ONDO token is verified on Etherscan. OUSG/USDY/Global Markets contracts are verified with public GitHub and audit code available. The deployed ONDO token uses AccessControl, which differs from the public ondo-v1 repository.", - "criteria": [ - { - "id": "verifiability__token-source", - "name": "Token Contract Source Verification", - "about": "Determines whether the token contract's source code is publicly available and verifiably matches the deployed bytecode.", - "status": "positive", - "notes": "The ONDO token contract is verified on Etherscan. The deployed contract uses AccessControl, which differs from the simpler Ownable-based contract in the public ondo-v1 repository.", - "evidence": [ - { - "urls": [ - { - "name": "ONDO Token (Etherscan verified)", - "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#code", - "type": "explorer" - }, - { - "name": "Public ondo-v1 repo (differs from deployed)", - "url": "https://github.com/ondoprotocol/ondo-v1/blob/main/contracts/tokens/Ondo.sol", - "type": "github" - } - ] - } - ] - }, - { - "id": "verifiability__protocol-source", - "name": "Protocol Component Source Verification", - "about": "Determines whether core protocol contracts are publicly accessible and verifiable against their onchain deployments.", - "status": "positive", - "notes": "OUSG, USDY, and Global Markets contracts are verified on Etherscan. Public GitHub repositories and audit code available.", - "evidence": [ - { - "name": "OUSG/USDY", - "urls": [ - { - "name": "OUSG", - "url": "https://etherscan.io/address/0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92#code", - "type": "explorer" - }, - { - "name": "USDY", - "url": "https://etherscan.io/address/0x96F6eF951840721AdBF46Ac996b59E0235CB985C#code", - "type": "explorer" - }, - { - "name": "USDY GitHub", - "url": "https://github.com/ondoprotocol/usdy", - "type": "github" - }, - { - "name": "Audit code (Code4rena)", - "url": "https://github.com/code-423n4/2024-03-ondo-finance", - "type": "github" - } - ] - }, - { - "name": "Global Markets", - "urls": [ - { - "name": "GMTokenManager", - "url": "https://etherscan.io/address/0x2c158BC456e027b2AfFCCadF1BDBD9f5fC4c5C8c#code", - "type": "explorer" - }, - { - "name": "USDon", - "url": "https://etherscan.io/address/0xAcE8E719899F6E91831B18AE746C9A965c2119F1#code", - "type": "explorer" - }, - { - "name": "USDonManager", - "url": "https://etherscan.io/address/0x05CCbB4b74854f8A067b83475E8c34f5a413D7e1#code", - "type": "explorer" - } - ] - } - ] - } - ] - }, - { - "id": "distribution", - "name": "Token Distribution", - "tags": ["Metric 4"], - "about": "This metric evaluates whether ownership and therefore effective voting power in governance is meaningfully distributed.", - "summary": "~59% of ONDO supply is held by a single team multisig, giving effective governance control. Vesting schedules exist per documentation but specific addresses are not publicly verifiable onchain.", - "criteria": [ - { - "id": "distribution__concentration", - "name": "Ownership Concentration", - "about": "Measures whether a single actor or coordinated group controls a majority of the voting supply.", - "status": "at_risk", - "notes": "~59% of ONDO supply is held by a single team multisig. This gives the team unilateral control over governance. The team can pass any proposal and block any proposal.", - "evidence": [ - { - "name": "Team Holdings", - "summary": "Team Multisig balance: ~5.9B ONDO (~59% of supply).\nTotal supply: 10B ONDO.\nMultisig config: 4-of-7.\n\nThis multisig also holds DEFAULT_ADMIN_ROLE and MINTER_ROLE on the ONDO token. \"Decentralized governance\" is effectively team governance.", - "urls": [ - { - "name": "Team Multisig holdings", - "url": "https://etherscan.io/address/0x677fd4ed8ae623f2f625deb2d64f2070e46ca1a1", - "type": "explorer" - }, - { - "name": "ONDO Token totalSupply", - "url": "https://etherscan.io/address/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3#readContract", - "type": "explorer" - } - ] - } - ] - }, - { - "id": "distribution__supply-schedule", - "name": "Future Token Unlocks", - "about": "Are there known, future events (such as vesting cliffs) that will materially affect the concentration of tokens?", - "status": "warning", - "notes": "Per documentation, unlock schedules exist for team and investors with unclear start dates, but the token being transferrable from Jan 2024 allows an educated guess of many unlocks yet to pan out. Aragon has not been able to verify specific vesting contract addresses from onchain data.", - "evidence": [ - { - "name": "Documented Schedule", - "summary": "CoinList Tranche 1 (~0.3%): 1-year lock, then 18-month linear release. \n\nCoinList Tranche 2 (~1.7%): 1-year lock, then 6-month linear release. \n\nSeed Investors (<7%): 1-year cliff, then 48-month release. \n\nSeries A (<7%): 1-year cliff, then 48-month release. \n\nCore Team: 5-year extended lock-up from the transfer unlock.\n\nAragon has not been able to verify specific vesting contract addresses or unlock schedules from onchain data.", - "urls": [ - { - "name": "Token Documentation", - "url": "https://docs.ondo.foundation/ondo-token", - "type": "docs" - }, - { - "name": "CoinList - ONDO Community Sale", - "url": "https://coinlist.co/ondo", - "type": "docs" - } - ] - } - ] - } - ] - }, - { - "id": "offchain", - "name": "Offchain Dependencies", - "tags": [ - "Reference" - ], - "about": "Economically material assets and obligations often sit offchain, outside the custody and direct enforcement of a token, while still determining which interfaces and domains most users reach, where fees are charged or captured, who can commercialize or restrict the software, and who can sign contracts or move offchain funds.", - "summary": "Ondo Finance Inc. operates the primary website, documentation, APIs, dashboards, and technical interfaces under its Terms of Service. The Terms preserve Ondo-related trademark/IP rights and separate interface services from product issuance or economic terms. Certain product source files use BUSL-1.1 SPDX headers, but no tokenholder-controlled licensing right was identified.", - "criteria": [ - { - "id": "offchain__trademark", - "name": "Trademark", - "about": "Are core trademarks and brand assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Ondo's Terms of Service state that Ondo names, logos, and marks used on the site or services are owned by Ondo, its affiliates, Covered Entities, or applicable licensors.", - "evidence": [ - { - "name": "Ondo Terms of Service - Proprietary Rights", - "summary": "The Terms reserve Ondo names, logos, and marks to Ondo, its affiliates, Covered Entities, or applicable licensors, and do not grant users rights in those trademarks.", - "urls": [ - { - "name": "Terms of Service", - "url": "https://docs.ondo.finance/legal/terms-of-service", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__distribution", - "name": "Distribution", - "about": "Are primary domains and distribution assets owned or controlled by a tokenholder-controlled legal entity?", - "status": "warning", - "notes": "Ondo Finance Inc. controls the primary website, documentation, APIs, dashboards, and technical interfaces. The Terms of Service identify Ondo Finance Inc. as the contracting party for those interface services, while product issuance and economic terms are governed separately by Covered Entity terms.", - "evidence": [ - { - "name": "Ondo Terms of Service - Interface Services", - "summary": "Ondo Finance Inc. operates the site and interface services. The Terms also state that Covered Entities are separate legal entities providing their own services under their own terms.", - "urls": [ - { - "name": "Terms of Service", - "url": "https://docs.ondo.finance/legal/terms-of-service", - "type": "docs" - } - ] - } - ] - }, - { - "id": "offchain__licensing", - "name": "Licensing", - "about": "Is core protocol software/IP (and any associated licensing rights, where applicable) owned or controlled by a tokenholder-controlled legal entity?", - "status": "unevaluated", - "notes": "Certain USDY/rOUSG source files use SPDX-License-Identifier: BUSL-1.1. BUSL-1.1 is not an open-source license and restricts production/commercial use until the applicable change date or open-source conversion.", - "evidence": [ - { - "name": "BUSL-1.1 Source Headers", - "summary": "USDY and rOUSG source files include BUSL-1.1 SPDX headers. No repo-level license file or tokenholder-controlled license holder was identified.", - "urls": [ - { - "name": "USDY source SPDX header", - "url": "https://github.com/ondoprotocol/usdy/blob/main/contracts/usdy/USDY.sol#L1", - "type": "github" - }, - { - "name": "rOUSG source SPDX header", - "url": "https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L1", - "type": "github" - }, - { - "name": "BUSL-1.1 license text", - "url": "https://spdx.org/licenses/BUSL-1.1.html", - "type": "docs" - } - ] - } - ] - } - ] - } - ] -} diff --git a/src/data/tokens.json b/src/data/tokens.json deleted file mode 100644 index e619bc2..0000000 --- a/src/data/tokens.json +++ /dev/null @@ -1,279 +0,0 @@ -{ - "tokens": [ - { - "id": "aave", - "coingeckoId": "aave", - "name": "AAVE", - "symbol": "AAVE", - "address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", - "icon": "https://assets.coingecko.com/coins/images/12645/standard/aave-token-round.png", - "description": "AAVE is the native token of Aave, the largest lending protocol in DeFi.", - "network": "ethereum", - "evidenceEntries": 18, - "positive": 14, - "neutral": 3, - "atRisk": 1, - "lastUpdated": 1775651992, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://aave.com/", - "twitter": "https://twitter.com/aave", - "scan": "https://etherscan.io/token/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9" - }, - "infoDescription": "Aave is an Open Source Protocol to create Non-Custodial Liquidity Markets to earn interest on supplying and borrowing assets with a variable interest rate." - }, - { - "id": "aero", - "coingeckoId": "aerodrome-finance", - "name": "AERO", - "symbol": "AERO", - "address": "0x940181a94A35A4569E4529A3CDfB74e38FD98631", - "icon": "https://assets.coingecko.com/coins/images/31745/standard/token.png", - "description": "AERO is the native token of the Aerodrome protocol, a ve(3,3) MetaDex on Base with largely immutable contracts and programmatic value flows to veAERO holders.", - "network": "base", - "evidenceEntries": 22, - "positive": 19, - "neutral": 2, - "atRisk": 1, - "lastUpdated": 1775651992, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://aerodrome.finance/", - "twitter": "https://twitter.com/Aerodrome", - "scan": "https://basescan.org/token/0x940181a94A35A4569E4529A3CDfB74e38FD98631" - }, - "infoDescription": "Aerodrome is a decentralized exchange where you can execute low-fee swaps, deposit tokens to earn rewards, and actively participate in the onchain economy." - }, - { - "id": "crv", - "coingeckoId": "curve-dao-token", - "name": "CRV", - "symbol": "CRV", - "address": "0xD533a949740bb3306d119CC777fa900bA034cd52", - "icon": "https://assets.coingecko.com/coins/images/12124/standard/Curve.png", - "description": "CRV is the native token of the Curve protocol - a leading stablecoin DEX. The veCRV gauge system enables programmatic, onchain value direction by tokenholders.", - "network": "ethereum", - "evidenceEntries": 4, - "positive": 4, - "neutral": 0, - "atRisk": 0, - "lastUpdated": 1775651992, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://curve.finance/", - "twitter": "https://twitter.com/curvefinance", - "scan": "https://etherscan.io/token/0xD533a949740bb3306d119CC777fa900bA034cd52" - }, - "infoDescription": "Curve is a decentralized exchange (DEX) and automated market maker (AMM) on Ethereum and EVM-compatible sidechains/L2s, designed for the efficient trading of stablecoins and volatile assets." - }, - { - "id": "ena", - "coingeckoId": "ethena", - "name": "ENA", - "symbol": "ENA", - "address": "0x57e114B691Db790C35207b2e685D4A43181e6061", - "icon": "https://assets.coingecko.com/coins/images/36530/standard/ethena.png", - "description": "ENA is the governance token of Ethena, a synthetic dollar protocol. Governance is advisory via Snapshot signaling; the Dev Multisig executes all decisions. Fee switch received positive forum signals but awaits Snapshot vote and onchain execution.", - "network": "ethereum", - "evidenceEntries": 18, - "positive": 2, - "neutral": 0, - "atRisk": 16, - "lastUpdated": 1775651992, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://ethena.fi/", - "twitter": "https://twitter.com/ethena", - "scan": "https://etherscan.io/token/0x57e114B691Db790C35207b2e685D4A43181e6061" - }, - "infoDescription": "Ethena is a synthetic dollar protocol built on Ethereum, offering a crypto-native alternative to traditional stablecoins via its USDe token and yield-bearing sUSDe." - }, - { - "id": "ldo", - "coingeckoId": "lido-dao", - "name": "LDO", - "symbol": "LDO", - "address": "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32", - "icon": "https://assets.coingecko.com/coins/images/13573/standard/Lido_DAO.png", - "description": "LDO is the native token of Lido, a liquid staking protocol with onchain voting, safeguarded by stETH holders via Dual Governance.", - "network": "ethereum", - "evidenceEntries": 12, - "positive": 8, - "neutral": 3, - "atRisk": 1, - "lastUpdated": 1777631939, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://lido.fi/", - "twitter": "https://twitter.com/lidofinance", - "scan": "https://etherscan.io/token/0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32" - }, - "infoDescription": "Lido is the leading liquid staking solution for Ethereum." - }, - { - "id": "lqty", - "coingeckoId": "liquity", - "name": "LQTY", - "symbol": "LQTY", - "address": "0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D", - "icon": "https://assets.coingecko.com/coins/images/14665/standard/logo_V2.png", - "description": "LQTY is the secondary token of the Liquity protocol - a decentralised, immutable, and governance-free borrowing protocol that issues the LUSD (V1) and BOLD (V2) stablecoins.", - "network": "ethereum", - "evidenceEntries": 0, - "positive": 0, - "neutral": 0, - "atRisk": 0, - "lastUpdated": 1778064256, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://www.liquity.org/", - "twitter": "https://twitter.com/LiquityProtocol", - "scan": "https://etherscan.io/token/0x6DEa81C8171D0bA574754EF6F8b412F2Ed88c54D" - }, - "infoDescription": "Liquity is a decentralised, immutable borrowing protocol that lets users mint stablecoins (LUSD in V1, BOLD in V2) against ETH and LST collateral with no governance over core parameters." - }, - { - "id": "uni", - "coingeckoId": "uniswap", - "name": "UNI", - "symbol": "UNI", - "address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", - "icon": "https://assets.coingecko.com/coins/images/12504/standard/uniswap-logo.png", - "description": "UNI is the governance token of the Uniswap DAO, the DAO governing the Uniswap protocol - the largest decentralised exchange in DeFi.", - "network": "ethereum", - "evidenceEntries": 18, - "positive": 13, - "neutral": 4, - "atRisk": 1, - "lastUpdated": 1775651992, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://uniswap.org/", - "twitter": "https://twitter.com/uniswap", - "scan": "https://etherscan.io/token/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" - }, - "infoDescription": "Uniswap is the largest onchain marketplace. Buy and sell crypto on Ethereum and 16+ other chains." - }, - { - "id": "yb", - "coingeckoId": "yield-basis", - "name": "YB", - "symbol": "YB", - "address": "0x01791F726B4103694969820be083196cC7c045fF", - "icon": "https://coin-images.coingecko.com/coins/images/54871/small/yieldbasis_400x400.png", - "description": "YB is the token of YieldBasis. veYB holders control protocol governance through Aragon, direct YB emissions via gauge voting, and receive protocol admin fees.", - "network": "ethereum", - "evidenceEntries": 18, - "positive": 11, - "neutral": 3, - "atRisk": 4, - "lastUpdated": 1774549304, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://yieldbasis.com/", - "twitter": "https://x.com/yieldbasis", - "scan": "https://etherscan.io/token/0x01791F726B4103694969820be083196cC7c045fF" - }, - "infoDescription": "Yield Basis is the liquidity protocol designed to eliminate Impermanent Loss (IL) in AMMs using constantly-maintained 2x leveraged liquidity provision." - }, - { - "id": "sky", - "coingeckoId": "sky", - "name": "SKY", - "symbol": "SKY", - "address": "0x56072C95FAA701256059aa122697B133aDEd9279", - "icon": "https://assets.coingecko.com/coins/images/39925/standard/sky.jpg", - "description": "SKY demonstrates strong ownership characteristics with binding onchain governance and active value accrual through buybacks. The token is non-upgradeable with no censorship capabilities. Trademarks remain with an independent foundation.", - "network": "ethereum", - "evidenceEntries": 18, - "positive": 13, - "neutral": 3, - "atRisk": 2, - "lastUpdated": 1774549304, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://sky.money/", - "twitter": "https://twitter.com/SkyEcosystem", - "scan": "https://etherscan.io/token/0x56072C95FAA701256059aa122697B133aDEd9279" - }, - "infoDescription": "Sky Protocol (formerly MakerDAO) issues two stablecoins: DAI (non-upgradeable) and USDS (upgradeable)." - }, - { - "id": "ethfi", - "coingeckoId": "ether-fi", - "name": "ETHFI", - "symbol": "ETHFI", - "address": "0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB", - "icon": "https://assets.coingecko.com/coins/images/35958/standard/etherfi.jpeg", - "description": "EtherFi runs restaking infrastructure, liquid vaults, and savings products β€” expanding from LST into neobank territory. Governance is offchain with multisig execution. An active buyback program distributes purchased ETHFI to sETHFI holders; any funding from broader protocol revenue is currently discretionary.", - "network": "ethereum", - "evidenceEntries": 18, - "positive": 3, - "neutral": 13, - "atRisk": 2, - "lastUpdated": 1774549304, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://ether.fi/", - "twitter": "https://twitter.com/ether_fi", - "scan": "https://etherscan.io/token/0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB" - }, - "infoDescription": "ether.fi is a liquid restaking protocol that enables users to stake ETH while maintaining liquidity through eETH tokens and participating in EigenLayer restaking." - }, - { - "id": "ondo", - "name": "ONDO", - "symbol": "ONDO", - "address": "0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3", - "icon": "https://assets.coingecko.com/coins/images/26580/standard/ONDO.png", - "description": "Ondo Finance tokenizes real-world assets: OUSG (US Treasuries), USDY (yield-bearing stablecoin), and Global Markets (tokenized equities). ONDO token has no control over these core products; team multisigs govern all ~$3.5B TVL.", - "network": "ethereum", - "evidenceEntries": 18, - "positive": 3, - "neutral": 2, - "atRisk": 13, - "lastUpdated": 1778674909, - "updatedBy": { - "avatar": "/logo192.png", - "name": "Aragon Developers" - }, - "links": { - "website": "https://ondo.finance/", - "twitter": "https://twitter.com/OndoFinance", - "scan": "https://etherscan.io/token/0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3" - }, - "infoDescription": "Ondo Finance is a tokenized real-world asset (RWA) protocol with ~$3.56B TVL across OUSG, USDY, and Global Markets. ONDO token governs Flux Finance (~$43M TVL), a Compound V2 fork for permissioned lending.", - "coingeckoId": "ondo-finance" - } - ] -} diff --git a/src/hooks/use-market-data.ts b/src/hooks/use-market-data.ts index 7f46bcd..38b1efe 100644 --- a/src/hooks/use-market-data.ts +++ b/src/hooks/use-market-data.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query" -import { fetchMarketDataFn } from "@/routes/api.market-data" import type { Token } from "@/lib/token-data" +import { fetchMarketDataFn } from "@/routes/api.market-data" export interface EnrichedToken extends Token { marketCap?: number diff --git a/src/lib/data-fetcher.ts b/src/lib/data-fetcher.ts deleted file mode 100644 index 2adeb38..0000000 --- a/src/lib/data-fetcher.ts +++ /dev/null @@ -1,166 +0,0 @@ -/** - * GitHub Data Fetcher - * - * Fetches data files from GitHub repository for dynamic data updates - * without requiring app redeployment. - */ - -// Data source configuration -export const DATA_SOURCES = { - tokens: { - owner: "aragon", - repo: "ownership-token-index-framework", - branch: "develop", - path: "data/tokens.json", - }, - metrics: { - owner: "aragon", - repo: "ownership-token-index-framework", - branch: "develop", - path: "data/metrics.json", - }, - framework: { - owner: "aragon", - repo: "ownership-token-index-framework", - branch: "develop", - path: "data/framework.json", - }, - faq: { - owner: "aragon", - repo: "ownership-token-index-framework", - branch: "develop", - path: "data/faq.json", - }, -} as const - -export type DataSourceKey = keyof typeof DATA_SOURCES - -// In-memory cache -const dataCache = new Map() - -// Cache duration (5 minutes) -const CACHE_DURATION_MS = 5 * 60 * 1000 - -/** - * Build GitHub raw content URL from data source config - */ -function buildGitHubRawUrl( - source: (typeof DATA_SOURCES)[DataSourceKey] -): string { - return `https://raw.githubusercontent.com/${source.owner}/${source.repo}/${source.branch}/${source.path}` -} - -/** - * Check if cached data is still valid - */ -function isCacheValid(cacheEntry: { timestamp: number } | undefined): boolean { - if (!cacheEntry) return false - return Date.now() - cacheEntry.timestamp < CACHE_DURATION_MS -} - -/** - * Fetch data from GitHub with caching - * - * @param sourceKey - The data source key from DATA_SOURCES - * @param options - Fetch options - * @returns Parsed JSON data - * - * @example - * const tokens = await fetchGitHubData('tokens') - * const metrics = await fetchGitHubData('metrics', { cache: 'no-store' }) - */ -export async function fetchGitHubData( - sourceKey: DataSourceKey, - options?: { - /** Bypass cache and force fresh fetch */ - bypassCache?: boolean - /** Additional fetch options */ - fetchOptions?: RequestInit - } -): Promise { - const source = DATA_SOURCES[sourceKey] - const url = buildGitHubRawUrl(source) - const cacheKey = url - - // Check cache first (unless bypassed) - if (!options?.bypassCache) { - const cached = dataCache.get(cacheKey) - if (cached && isCacheValid(cached)) { - return cached.data as T - } - } - - try { - // Fetch from GitHub - const response = await fetch(url, { - ...options?.fetchOptions, - headers: { - Accept: "application/json", - ...options?.fetchOptions?.headers, - }, - }) - - if (!response.ok) { - throw new Error( - `Failed to fetch ${sourceKey} from GitHub: ${response.status} ${response.statusText}` - ) - } - - const data = (await response.json()) as T - - // Update cache - dataCache.set(cacheKey, { - data, - timestamp: Date.now(), - }) - - return data - } catch (error) { - // If fetch fails and we have stale cache, return it - const staleCache = dataCache.get(cacheKey) - if (staleCache) { - console.warn( - `Failed to fetch fresh data for ${sourceKey}, using stale cache`, - error - ) - return staleCache.data as T - } - - throw error - } -} - -/** - * Clear cache for a specific data source or all sources - */ -export function clearDataCache(sourceKey?: DataSourceKey): void { - if (sourceKey) { - const source = DATA_SOURCES[sourceKey] - const url = buildGitHubRawUrl(source) - dataCache.delete(url) - } else { - dataCache.clear() - } -} - -/** - * Prefetch all data sources (useful for build time or app initialization) - */ -export async function prefetchAllData(): Promise { - const keys = Object.keys(DATA_SOURCES) as DataSourceKey[] - await Promise.all(keys.map((key) => fetchGitHubData(key))) -} - -/** - * Get cache statistics - */ -export function getCacheStats() { - return { - size: dataCache.size, - entries: Array.from(dataCache.entries()).map(([url, entry]) => ({ - url, - age: Date.now() - entry.timestamp, - valid: isCacheValid(entry), - })), - } -} diff --git a/src/lib/faq-data.ts b/src/lib/faq-data.ts index 3579560..207c558 100644 --- a/src/lib/faq-data.ts +++ b/src/lib/faq-data.ts @@ -1,19 +1,8 @@ -import faqData from "@/data/faq.json" +import faqData from "@/data/generated/faq.json" +import type { FaqQuestion, FaqTopic } from "@/lib/schemas" -export interface FaqQuestion { - id: string - question: string - answer: string -} - -export interface FaqTopic { - id: string - name: string - about?: string - questions: FaqQuestion[] -} +export type { FaqQuestion, FaqTopic } export function getFaqTopics(): FaqTopic[] { - const baseTopics = faqData.topics as FaqTopic[] - return baseTopics + return faqData.topics as FaqTopic[] } diff --git a/src/lib/framework.ts b/src/lib/framework.ts index 3786650..4ba011d 100644 --- a/src/lib/framework.ts +++ b/src/lib/framework.ts @@ -1,37 +1,37 @@ -import frameworkData from "@/data/framework.json" +import { publishedFrameworkQuery } from "@/lib/published-queries" +import { queryClient } from "@/lib/query-client" +import type { FrameworkDoc } from "@/lib/schemas" -export interface FrameworkMetric { - id: string - name: string - about: string - criteria: FrameworkCriteria[] -} +export type FrameworkMetric = FrameworkDoc["metrics"][number] +export type FrameworkCriteria = FrameworkMetric["criteria"][number] -export interface FrameworkCriteria { - id: string - name: string - about: string +/** + * Synchronous read over the hydrated query cache; the root route loader + * ensures the framework doc on every page. Fails loudly on a cache miss. + */ +function readFramework(): FrameworkDoc { + const data = queryClient.getQueryData(publishedFrameworkQuery.queryKey) + if (!data) { + throw new Error( + "Published framework doc is not in the query cache β€” a route loader must ensure publishedFrameworkQuery before render" + ) + } + return data } -export const FRAMEWORK_BASE_URL = - "https://github.com/aragon/ownership-token-framework/blob/development/README.md" - /** - * Map metric IDs to their corresponding README section anchors + * Base URL of the framework README. Data-owned (content/framework/_meta.json), + * so it is a render-time read, not a module constant. */ -const METRIC_ANCHORS: Record = { - "onchain-ctrl": "#metric-1-onchain-control", - "val-accrual": "#metric-2-value-accrual", - verifiability: "#metric-3-verifiability", - distribution: "#metric-4-token-distribution", - offchain: "#offchain-dependencies", +export function getFrameworkBaseUrl(): string { + return readFramework().baseUrl } /** * Get framework metric definition by ID */ export function getFrameworkMetric(metricId: string): FrameworkMetric | null { - const metric = frameworkData.find((m) => m.id === metricId) + const metric = readFramework().metrics.find((m) => m.id === metricId) return metric || null } @@ -41,7 +41,7 @@ export function getFrameworkMetric(metricId: string): FrameworkMetric | null { export function getFrameworkCriteria( criteriaId: string ): FrameworkCriteria | null { - for (const metric of frameworkData) { + for (const metric of readFramework().metrics) { const criteria = metric.criteria.find((c) => c.id === criteriaId) if (criteria) return criteria } @@ -52,13 +52,14 @@ export function getFrameworkCriteria( * Get all framework metrics */ export function getAllFrameworkMetrics(): FrameworkMetric[] { - return frameworkData + return readFramework().metrics } /** * Get the framework URL for a specific metric, with anchor to its section */ export function getFrameworkUrl(metricId: string): string { - const anchor = METRIC_ANCHORS[metricId] - return anchor ? `${FRAMEWORK_BASE_URL}${anchor}` : FRAMEWORK_BASE_URL + const anchor = getFrameworkMetric(metricId)?.anchor + const baseUrl = getFrameworkBaseUrl() + return anchor ? `${baseUrl}${anchor}` : baseUrl } diff --git a/src/lib/metrics-data.ts b/src/lib/metrics-data.ts index c399f9a..45b2579 100644 --- a/src/lib/metrics-data.ts +++ b/src/lib/metrics-data.ts @@ -1,96 +1,51 @@ -import metricsData from "@/data/metrics.json" -import { getFrameworkCriteria, getFrameworkMetric } from "@/lib/framework" -import type { EvidenceLinkType } from "../components/ui/evidence-link.tsx" - -export const CRITERIA_STATUS = { - POSITIVE: "positive", - WARNING: "warning", - AT_RISK: "at_risk", - UNEVALUATED: "unevaluated", - REFERENCE: "reference", -} as const - -export type CriteriaStatusValue = - (typeof CRITERIA_STATUS)[keyof typeof CRITERIA_STATUS] - -export function normalizeCriteriaStatus( - status?: string -): CriteriaStatusValue { - if ( - status === CRITERIA_STATUS.POSITIVE || - status === CRITERIA_STATUS.WARNING || - status === CRITERIA_STATUS.AT_RISK || - status === CRITERIA_STATUS.UNEVALUATED || - status === CRITERIA_STATUS.REFERENCE - ) { - return status +import { publishedTokenDocQuery } from "@/lib/published-queries" +import { queryClient } from "@/lib/query-client" +import { + type ComposedCriterion, + type ComposedMetric, + CRITERIA_STATUS, + type CriteriaStatusValue, + type Evidence, + type EvidenceUrl, + type TokenDoc, +} from "@/lib/schemas" +import { getTokenById } from "@/lib/token-data" + +export { CRITERIA_STATUS } +export type { CriteriaStatusValue, Evidence, EvidenceUrl } +export type Criteria = ComposedCriterion +export type Metric = ComposedMetric + +/** + * Synchronous read over the hydrated query cache. The token detail route + * loader ensures the doc; an unensured read fails loudly by design. + * A cached `null` means the token does not exist (valid, returns null). + */ +export function getTokenDoc(tokenId: string): TokenDoc | null { + const { queryKey } = publishedTokenDocQuery(tokenId) + const doc = queryClient.getQueryData(queryKey) + if (doc === undefined) { + throw new Error( + `Token doc "${tokenId}" is not in the query cache β€” the route loader must ensure publishedTokenDocQuery` + ) } - return CRITERIA_STATUS.REFERENCE -} - -export interface EvidenceUrl { - name: string - url: string - type?: EvidenceLinkType -} - -export interface Evidence { - name?: string - summary?: string - urls: EvidenceUrl[] -} - -export interface Criteria { - id: string - name: string - about: string - status: string - notes: string - tags?: string[] - evidence: EvidenceUrl[] | Evidence[] -} - -export interface Metric { - id: string - name: string - about: string - summary: string - tags?: string[] - criteria: Criteria[] + return doc } +/** + * Cross-token criterion status, served from the index read model (the + * dashboard renders single-criterion columns across all tokens without + * loading per-token docs). Statuses are schema-validated canonical values + * (ADR 0002) β€” no runtime normalization layer exists by design. + */ export function getCriteriaStatus( tokenId: string, criteriaId: string ): CriteriaStatusValue { - const metrics = metricsData[tokenId as keyof typeof metricsData] - if (!metrics) return CRITERIA_STATUS.REFERENCE - for (const m of metrics as Metric[]) { - for (const c of m.criteria) { - if (c.id === criteriaId) return normalizeCriteriaStatus(c.status) - } - } - return CRITERIA_STATUS.REFERENCE + const row = getTokenById(tokenId) + return row?.criteriaStatuses[criteriaId] ?? CRITERIA_STATUS.REFERENCE } export function getMetricsByTokenId(tokenId: string): Metric[] { - const metrics = metricsData[tokenId as keyof typeof metricsData] - if (!metrics) return [] - - return (metrics as Metric[]).map((metric) => { - const frameworkMetric = getFrameworkMetric(metric.id) - - return { - ...metric, - about: frameworkMetric?.about || metric.about, - criteria: metric.criteria.map((criteria) => { - const frameworkCriteria = getFrameworkCriteria(criteria.id) - - return { - ...criteria, - about: frameworkCriteria?.about || criteria.about, - } - }), - } - }) + return getTokenDoc(tokenId)?.metrics ?? [] } diff --git a/src/lib/published-queries.ts b/src/lib/published-queries.ts new file mode 100644 index 0000000..eb103e0 --- /dev/null +++ b/src/lib/published-queries.ts @@ -0,0 +1,111 @@ +/** + * Query definitions for the published read models. + * + * Server side (`import.meta.env.SSR` is statically true), queryFns read the + * published source directly β€” the SAME seam the API uses (published-source), + * so SSR/UI and /api/v1 never disagree: both serve committed data by default + * and both serve the release snapshot when release mode is on. No self-HTTP, + * and Vite's static replacement drops the dynamic import (and the data behind + * it) from the client build entirely. + * + * Client side, queryFns fetch the canonical JSON endpoints β€” the app is the + * first consumer of its own published API (/api/v1/tokens*). + * + * Published data is immutable per snapshot, so staleTime/gcTime are Infinity: + * a new snapshot arrives as a new deployment, or β€” in release mode β€” as the + * next SSR render reading a newer release, never as a background refetch. + */ +import { + type FetchQueryOptions, + type QueryClient, + type QueryKey, + queryOptions, +} from "@tanstack/react-query" +import type { FrameworkDoc, IndexRow, TokenDoc } from "@/lib/schemas" + +async function fetchEnvelopeData(url: string): Promise { + const res = await fetch(url) + if (!res.ok) { + throw new Error(`Published data fetch failed: ${res.status} ${url}`) + } + const payload = (await res.json()) as { data: T } + return payload.data +} + +// Server-only. In release mode the published data can change within the process +// lifetime (a new GitHub Release), so the shared QueryClient + staleTime:Infinity +// would otherwise pin the first snapshot until restart. Statically false on the +// client (import.meta.env.SSR), so client caching is unchanged. +const RELEASE_MODE_SSR = + import.meta.env.SSR && process.env.OTF_PUBLISHED_RELEASE === "true" + +/** + * Ensure a published query is cached for this render. In release-mode SSR we + * force a revalidated read so the loader can't serve a process-pinned snapshot; + * the actual fetch is still deduped + 60s-cached by published-source, so this + * doesn't hammer GitHub. Committed mode and the client reuse the cached value + * (data is immutable per deploy there). + */ +export async function readPublished< + TQueryFnData, + TError, + TData, + TQueryKey extends QueryKey, +>( + client: QueryClient, + options: FetchQueryOptions +): Promise { + if (RELEASE_MODE_SSR) { + await client.fetchQuery({ ...options, staleTime: 0 }) + return + } + await client.ensureQueryData(options) +} + +export const publishedIndexQuery = queryOptions({ + queryKey: ["published", "index"], + queryFn: async (): Promise<{ tokens: IndexRow[] }> => { + if (import.meta.env.SSR) { + const { getIndex } = await import("@/lib/server/published-source") + return getIndex() + } + return fetchEnvelopeData("/api/v1/tokens") + }, + staleTime: Number.POSITIVE_INFINITY, + gcTime: Number.POSITIVE_INFINITY, +}) + +export const publishedFrameworkQuery = queryOptions({ + queryKey: ["published", "framework"], + queryFn: async (): Promise => { + if (import.meta.env.SSR) { + const { getFramework } = await import("@/lib/server/published-source") + return getFramework() + } + return fetchEnvelopeData("/api/v1/framework") + }, + staleTime: Number.POSITIVE_INFINITY, + gcTime: Number.POSITIVE_INFINITY, +}) + +export const publishedTokenDocQuery = (tokenId: string) => { + const normalizedId = tokenId.trim().toLowerCase() + return queryOptions({ + queryKey: ["published", "token-doc", normalizedId], + queryFn: async (): Promise => { + if (import.meta.env.SSR) { + const { getTokenDoc } = await import("@/lib/server/published-source") + return getTokenDoc(normalizedId) + } + const res = await fetch(`/api/v1/tokens/${normalizedId}`) + if (res.status === 404) return null + if (!res.ok) { + throw new Error(`Published data fetch failed: ${res.status}`) + } + const payload = (await res.json()) as { data: TokenDoc } + return payload.data + }, + staleTime: Number.POSITIVE_INFINITY, + gcTime: Number.POSITIVE_INFINITY, + }) +} diff --git a/src/lib/query-client.ts b/src/lib/query-client.ts new file mode 100644 index 0000000..73eb738 --- /dev/null +++ b/src/lib/query-client.ts @@ -0,0 +1,15 @@ +import { QueryClient } from "@tanstack/react-query" + +/** + * Shared QueryClient singleton. + * + * Client: the app-wide cache, hydrated from SSR via the router ssr-query + * integration (src/router.tsx). + * Server: shared across requests by design β€” the published data it caches is + * immutable per deployment snapshot (~90-day update cadence), so cross-request + * sharing is correct and saves rereads. + * + * Lives in its own module (not router.tsx) so data libs can import it without + * creating an import cycle through the route tree. + */ +export const queryClient = new QueryClient() diff --git a/src/lib/schemas/.vendor-lock.json b/src/lib/schemas/.vendor-lock.json new file mode 100644 index 0000000..865e057 --- /dev/null +++ b/src/lib/schemas/.vendor-lock.json @@ -0,0 +1,12 @@ +{ + "source": "aragon/otf-cms", + "source_commit": "467c059f22c9f18746bdb23993df8adbac769410", + "files": { + "src/lib/schemas/atoms.ts": "fa37e43dc9a43f4fa4597160b789d5f1abfbef362d4310f465a89dbd32ae8caf", + "src/lib/schemas/common.ts": "f24a880ed2a539baaa51f7ad142e77be3f4c600ab8db063dcb9879689157d1f4", + "src/lib/schemas/index.ts": "3a7200ea0eacf042c31b1548d9ed82b2bad1387114fe7a11a705afedd952ee6b", + "src/lib/schemas/read-models.ts": "9af495412eb22c6ac7a9ced07e976b9e960582f46e4dcf2c4f2dcc6f51868041", + "src/lib/schemas/readiness.ts": "31597d72f8c66cc4734838a4556e78bb6871054cb244af5a43d4612bbdc29f4c", + "scripts/lib/compose-data.ts": "7644578dfa2afa5944c4160da7d35b4f42a9704c7f4834976736d49e1c869642" + } +} diff --git a/src/lib/schemas/atoms.ts b/src/lib/schemas/atoms.ts new file mode 100644 index 0000000..4f99732 --- /dev/null +++ b/src/lib/schemas/atoms.ts @@ -0,0 +1,122 @@ +/** + * Write-model schemas β€” the editable atoms under `content/`. + * + * Atoms are research-shaped: small, flat, id-keyed. Anything derivable + * (status counts, scores, framework-owned `about` text) is intentionally + * absent and produced at composition time (scripts/compose-data.mjs). + */ +import { z } from "zod" +import { criteriaStatusSchema, evidenceSchema, tkUrlSchema } from "./common" + +/** content/tokens/.json β€” registry atom: identity, display, chain data. */ +export const tokenAtomSchema = z.strictObject({ + id: z.string(), + coingeckoId: z.string(), + name: z.string(), + symbol: z.string(), + address: z.string(), + /** Icon url, or "TK" while deferred (lenient so partial tokens compose). */ + icon: tkUrlSchema, + description: z.string(), + infoDescription: z.string(), + network: z.string(), + links: z.strictObject({ + website: tkUrlSchema, + twitter: tkUrlSchema, + scan: tkUrlSchema, + }), + /** Editorial provenance; future home is Git history (publish pipeline). */ + lastUpdated: z.number(), + updatedBy: z.strictObject({ + name: z.string(), + avatar: z.string(), + }), +}) + +/** content/evaluations///_metric.json β€” per-token metric editorial. */ +export const metricEditorialAtomSchema = z.strictObject({ + summary: z.string(), + tags: z.array(z.string()), +}) + +/** content/evaluations///.json */ +export const criterionEvaluationAtomSchema = z.strictObject({ + /** Display-name override; present only where it diverges from framework. */ + name: z.string().optional(), + status: criteriaStatusSchema, + notes: z.string(), + tags: z.array(z.string()).optional(), + evidence: z.array(evidenceSchema).optional(), +}) + +/** content/framework/.json β€” canonical metric definition. */ +export const frameworkCriterionSchema = z.strictObject({ + id: z.string(), + name: z.string(), + about: z.string(), +}) + +export const frameworkMetricAtomSchema = z.strictObject({ + id: z.string(), + /** Formal framework name (e.g. "Metric 1: Onchain Control"). */ + name: z.string(), + /** Clean display name used in composed token docs (e.g. "Onchain Control"). */ + displayName: z.string(), + about: z.string(), + /** README section anchor for getFrameworkUrl; absent β†’ base URL only. */ + anchor: z.string().optional(), + criteria: z.array(frameworkCriterionSchema), +}) + +/** content/framework/_meta.json β€” framework-wide constants. */ +export const frameworkMetaSchema = z.strictObject({ + baseUrl: z.string(), + /** Canonical metric ordering for composed docs. */ + order: z.array(z.string()), +}) + +/** content/faq.json */ +export const faqQuestionSchema = z.strictObject({ + id: z.string(), + question: z.string(), + answer: z.string(), +}) + +export const faqTopicSchema = z.strictObject({ + id: z.string(), + name: z.string(), + about: z.string().optional(), + questions: z.array(faqQuestionSchema), +}) + +export const faqSchema = z.strictObject({ + topics: z.array(faqTopicSchema), +}) + +/** content/testimonials.json */ +export const testimonialSchema = z.strictObject({ + id: z.string(), + name: z.string(), + organization: z.string(), + avatar: z.string(), + url: z.string(), + quote: z.string(), +}) + +export const testimonialsSchema = z.strictObject({ + title: z.string(), + testimonials: z.array(testimonialSchema), +}) + +export type TokenAtom = z.infer +export type MetricEditorialAtom = z.infer +export type CriterionEvaluationAtom = z.infer< + typeof criterionEvaluationAtomSchema +> +export type FrameworkCriterion = z.infer +export type FrameworkMetricAtom = z.infer +export type FrameworkMeta = z.infer +export type FaqQuestion = z.infer +export type FaqTopic = z.infer +export type Testimonial = z.infer +export type TestimonialsContent = z.infer diff --git a/src/lib/schemas/common.ts b/src/lib/schemas/common.ts new file mode 100644 index 0000000..e9133e6 --- /dev/null +++ b/src/lib/schemas/common.ts @@ -0,0 +1,55 @@ +import { z } from "zod" + +/** + * Publish-readiness sentinel. A field is either a real value or the literal + * "TK" (a deferral marker borrowed from editorial usage). Schemas stay lenient + * so partial tokens still validate and previews build; the publish gate + * (schemas/readiness.ts) treats "TK" β€” like empty and `unevaluated` β€” as + * UNRESOLVED and excludes the token from Release. + */ +export const TK = "TK" as const + +/** A url field that may be deferred with the TK sentinel. */ +export const tkUrlSchema = z.union([z.url(), z.literal(TK)]) + +/** A free-text field that may be deferred with the TK sentinel. */ +export const tkTextSchema = z.string() + +/** + * Canonical criteria workflow statuses β€” the ONLY valid vocabulary. + * Non-canonical values are a validation failure by design (ADR 0002): + * there is no runtime mapping/normalization layer for dialect statuses. + */ +export const CRITERIA_STATUS = { + POSITIVE: "positive", + WARNING: "warning", + AT_RISK: "at_risk", + UNEVALUATED: "unevaluated", + REFERENCE: "reference", +} as const + +export type CriteriaStatusValue = + (typeof CRITERIA_STATUS)[keyof typeof CRITERIA_STATUS] + +export const criteriaStatusSchema = z.enum([ + CRITERIA_STATUS.POSITIVE, + CRITERIA_STATUS.WARNING, + CRITERIA_STATUS.AT_RISK, + CRITERIA_STATUS.UNEVALUATED, + CRITERIA_STATUS.REFERENCE, +]) + +export const evidenceUrlSchema = z.strictObject({ + name: z.string(), + url: tkUrlSchema, + type: z.enum(["docs", "explorer", "github", "vote", "website"]).optional(), +}) + +export const evidenceSchema = z.strictObject({ + name: z.string().optional(), + summary: z.string().optional(), + urls: z.array(evidenceUrlSchema), +}) + +export type EvidenceUrl = z.infer +export type Evidence = z.infer diff --git a/src/lib/schemas/index.ts b/src/lib/schemas/index.ts new file mode 100644 index 0000000..a825e6a --- /dev/null +++ b/src/lib/schemas/index.ts @@ -0,0 +1,12 @@ +/** + * OTF data contract β€” a checked-in COPY of otf-cms's source (i.e. "vendored"). + * DO NOT EDIT HERE: a change to this copy fails CI's drift check. + * + * Source of truth: https://github.com/aragon/otf-cms (copied at 467c059f22c9f18746bdb23993df8adbac769410). + * To change it, edit the file in otf-cms, then refresh the copies here by + * re-running: node scripts/vendor-schemas.mjs + */ +export * from "./atoms" +export * from "./common" +export * from "./read-models" +export * from "./readiness" diff --git a/src/lib/schemas/read-models.ts b/src/lib/schemas/read-models.ts new file mode 100644 index 0000000..2a1371f --- /dev/null +++ b/src/lib/schemas/read-models.ts @@ -0,0 +1,150 @@ +/** + * Read-model schemas β€” the composed, consumer-shaped output under + * `src/data/generated/` (produced by scripts/compose-data.ts). + * + * These are the shapes the app renders and that the publish pipeline serves + * from GitHub Release snapshots β€” transport changes later, shape doesn't. + */ +import { z } from "zod" +import { tokenAtomSchema } from "./atoms" +import { criteriaStatusSchema, evidenceSchema } from "./common" + +/** A criterion as rendered: framework `about` merged, canonical status. */ +export const composedCriterionSchema = z.strictObject({ + id: z.string(), + name: z.string(), + about: z.string(), + status: criteriaStatusSchema, + notes: z.string(), + tags: z.array(z.string()).optional(), + evidence: z.array(evidenceSchema).optional(), +}) + +/** A metric as rendered: display name + framework about + editorial summary. */ +export const composedMetricSchema = z.strictObject({ + id: z.string(), + name: z.string(), + about: z.string(), + summary: z.string(), + tags: z.array(z.string()), + criteria: z.array(composedCriterionSchema), +}) + +export const metricScoreSchema = z.strictObject({ + metricId: z.string(), + metricName: z.string(), + passing: z.number(), + total: z.number(), + percentage: z.number(), + evaluated: z.boolean(), + reference: z.boolean(), +}) + +export const tokenScoreSchema = z.strictObject({ + tokenId: z.string(), + passing: z.number(), + total: z.number(), + percentage: z.number(), + metrics: z.array(metricScoreSchema), +}) + +/** + * Derived status counts. NOTE: computed from evaluations at compose time β€” + * the hand-maintained counts in the legacy tokens.json were stale for all + * 11 tokens and were dropped in the atom migration. + */ +export const tokenCountsSchema = z.strictObject({ + positive: z.number(), + neutral: z.number(), + atRisk: z.number(), + evidenceEntries: z.number(), +}) + +/** + * generated/index.json row β€” the dashboard-table read model (legacy Token + * shape). Carries the FULL token score (per-metric breakdown feeds the score + * hover) and a flat criterion-status map (single-criterion columns across all + * tokens) so the dashboard needs only this one read model. + */ +export const indexRowSchema = z.strictObject({ + ...tokenAtomSchema.shape, + positive: z.number(), + neutral: z.number(), + atRisk: z.number(), + evidenceEntries: z.number(), + score: tokenScoreSchema, + criteriaStatuses: z.record(z.string(), criteriaStatusSchema), +}) + +export const indexSchema = z.strictObject({ + tokens: z.array(indexRowSchema), +}) + +/** generated/tokens/.json β€” the per-token reusable unit (index row + full metrics). */ +export const tokenDocSchema = z.strictObject({ + ...indexRowSchema.shape, + metrics: z.array(composedMetricSchema), +}) + +/** generated/framework.json β€” definitions + anchors + base URL. */ +export const frameworkDocSchema = z.strictObject({ + baseUrl: z.string(), + metrics: z.array( + z.strictObject({ + id: z.string(), + name: z.string(), + displayName: z.string(), + about: z.string(), + anchor: z.string().optional(), + criteria: z.array( + z.strictObject({ + id: z.string(), + name: z.string(), + about: z.string(), + }) + ), + }) + ), +}) + +/** generated/manifest.json β€” deterministic snapshot identity for the composed set. */ +export const manifestSchema = z.strictObject({ + /** Content hash over the composed output; future R2 snapshot key component. */ + snapshot_id: z.string(), + /** Most-recent editorial edit across the set (ISO); null if none carry one. */ + last_updated: z.string().nullable(), + tokens: z.array(z.string()), +}) + +/** + * Provenance envelope wrapped around every API response. + * - last_updated: when the content was last edited (from the CMS). + * - published_at: when this snapshot was published; stays null until the + * publish pipeline stamps real publishes. Kept distinct from last_updated. + */ +export const provenanceSchema = z.strictObject({ + snapshot_id: z.string(), + commit_ref: z.string(), + last_updated: z.string().nullable(), + published_at: z.string().nullable(), + source: z.enum(["generated", "release"]), +}) + +export const apiErrorSchema = z.strictObject({ + error: z.strictObject({ + code: z.string(), + message: z.string(), + }), +}) + +export type ComposedCriterion = z.infer +export type ComposedMetric = z.infer +export type MetricScore = z.infer +export type TokenScore = z.infer +export type TokenCounts = z.infer +export type IndexRow = z.infer +export type TokenDoc = z.infer +export type FrameworkDoc = z.infer +export type Manifest = z.infer +export type Provenance = z.infer +export type ApiError = z.infer diff --git a/src/lib/schemas/readiness.ts b/src/lib/schemas/readiness.ts new file mode 100644 index 0000000..9c0c586 --- /dev/null +++ b/src/lib/schemas/readiness.ts @@ -0,0 +1,207 @@ +/** + * Publish-readiness detector β€” the keystone of the two-tier (preview / Release) + * model. Given a COMPOSED token doc (read-model shape), it returns a granular + * list of every UNRESOLVED item. A token is publish-ready ⟺ that list is empty. + * + * "Unresolved" items carry a reason, and reasons split by SEVERITY: + * - BLOCKING (hold the token out of the production Release): + * - "tk": a field literally equal to the "TK" sentinel (explicit + * "not done" marker β€” the deliberate deferral signal) + * - "regression": a field that was non-empty in the last published Release + * and is now blank/removed (accidental-deletion guard; this + * reason is attached by the CI-side baseline diff, not here) + * - ADVISORY (surfaced on the PR for visibility, but shippable β€” legacy + * content is grandfathered, so these never gut the current board): + * - "empty": a field that is blank + * - "missing": a field absent from the doc + * + * `unevaluated` is NOT a gap: it's a complete verdict ("we have not + * evaluated"), tracked separately as coverage (see `coverage()`), never here. + * + * Hard-required identity (id/name/symbol) is enforced by Zod upstream, so a + * token can never compose without them β€” this layer governs the softer + * completeness (notes, evidence, optional identity, summaries) plus TK. + * + * Pure (no fs, no app imports) so the bundle gate, the preview app, and a CI + * comment can all call it. This module is vendored by the app verbatim β€” keep + * it dependency-light and self-contained. + */ +import { TK } from "./common" + +/** One unresolved field, addressed by a dotted path into the token doc. */ +export type UnresolvedItem = { + path: string + reason: "tk" | "empty" | "missing" | "regression" +} + +/** Coverage stat for a token: how many criteria are evaluated vs `unevaluated`. */ +export type Coverage = { evaluated: number; unevaluated: number; total: number } + +/** + * Reasons that hold a token out of the production Release. Everything else is + * advisory β€” reported on the PR, but the token still publishes (grandfathered). + */ +export const BLOCKING_REASONS: ReadonlySet = new Set([ + "tk", + "regression", +]) + +/** Identity fields that must be present, non-empty, and not "TK". */ +const IDENTITY_FIELDS = [ + "id", + "coingeckoId", + "name", + "symbol", + "address", + "icon", + "description", + "infoDescription", + "network", +] as const + +/** links.* sub-fields that must be present, non-empty, and not "TK". */ +const LINK_FIELDS = ["website", "twitter", "scan"] as const + +/** Statuses that don't require notes/evidence to be considered resolved. */ +const EXEMPT_STATUSES = new Set(["reference", "unevaluated"]) + +const isTk = (v: unknown): boolean => v === TK +const isBlank = (v: unknown): boolean => + v === undefined || v === null || (typeof v === "string" && v.trim() === "") + +/** + * Classify a required string field. Returns the unresolved reason, or null if + * the field carries a real value. Order matters: TK is reported as "tk" even + * though it is also non-blank, so editors see the sentinel called out. + */ +function classifyText(v: unknown): UnresolvedItem["reason"] | null { + if (v === undefined) return "missing" + if (isTk(v)) return "tk" + if (isBlank(v)) return "empty" + return null +} + +/** Classify a required url field (TK sentinel β†’ "tk", blank β†’ "empty"). */ +function classifyUrl(v: unknown): UnresolvedItem["reason"] | null { + if (v === undefined) return "missing" + if (isTk(v)) return "tk" + if (isBlank(v)) return "empty" + return null +} + +/** + * Return every unresolved item in a composed token doc. Empty ⟺ publish-ready. + * `doc` is typed loosely on purpose: this runs over composer output that has + * not necessarily passed Zod yet (partial tokens are schema-valid but WIP). + */ +export function unresolved(doc: any): UnresolvedItem[] { + const items: UnresolvedItem[] = [] + + // 1. Identity fields: present, non-empty, not "TK". + for (const field of IDENTITY_FIELDS) { + const reason = + field === "icon" ? classifyUrl(doc?.[field]) : classifyText(doc?.[field]) + if (reason) items.push({ path: field, reason }) + } + + // 2. links.* β€” url fields; a "TK" url is unresolved. + const links = doc?.links + if (isBlank(links) || typeof links !== "object") { + items.push({ + path: "links", + reason: links === undefined ? "missing" : "empty", + }) + } else { + for (const key of LINK_FIELDS) { + const reason = classifyUrl(links[key]) + if (reason) items.push({ path: `links.${key}`, reason }) + } + } + + // 3. Metrics: each criterion status must be evaluated; notes + evidence must + // be resolved for any verdict that isn't reference/unevaluated; each + // metric summary must be non-empty / not-TK. + const metrics: any[] = Array.isArray(doc?.metrics) ? doc.metrics : [] + for (const metric of metrics) { + const mId = metric?.id ?? "?" + + const summaryReason = classifyText(metric?.summary) + if (summaryReason) { + items.push({ path: `metrics.${mId}.summary`, reason: summaryReason }) + } + + const criteria: any[] = Array.isArray(metric?.criteria) + ? metric.criteria + : [] + for (const c of criteria) { + const cId = c?.id ?? "?" + const base = `metrics.${mId}.criteria.${cId}` + const status = c?.status + + // `unevaluated` is a COMPLETE verdict ("we have not evaluated"), not a + // gap β€” it's tracked as coverage (see `coverage()`), never as unresolved. + // Notes/evidence are only required for real verdicts; reference and + // unevaluated are exempt. + if (!EXEMPT_STATUSES.has(status)) { + const notesReason = classifyText(c?.notes) + if (notesReason) { + items.push({ path: `${base}.notes`, reason: notesReason }) + } + const evidence = c?.evidence + if (!Array.isArray(evidence) || evidence.length === 0) { + items.push({ path: `${base}.evidence`, reason: "empty" }) + } + } + } + } + + return items +} + +/** + * Blocking subset of unresolved items β€” the ones that hold a token out of the + * production Release. Advisory items (empty/unevaluated/missing) are excluded. + * Pass `extra` to fold in items computed elsewhere (e.g. the CI baseline diff's + * "regression" items) before deciding readiness. + */ +export function blockingUnresolved( + doc: any, + extra: UnresolvedItem[] = [] +): UnresolvedItem[] { + return [...unresolved(doc), ...extra].filter((i) => + BLOCKING_REASONS.has(i.reason) + ) +} + +/** + * A composed token doc is publish-ready ⟺ it has no BLOCKING unresolved items. + * Advisory gaps (legacy empty) are grandfathered and still ship. `extra` lets + * the caller include CI-computed regression items in the decision. + */ +export function isPublishReady( + doc: any, + extra: UnresolvedItem[] = [] +): boolean { + return blockingUnresolved(doc, extra).length === 0 +} + +/** + * Evaluation coverage for a token β€” how many criteria carry a real verdict vs + * are still `unevaluated`. This is informational ("12/18 evaluated"), NOT a + * gap: an `unevaluated` criterion is a complete, shippable answer. + */ +export function coverage(doc: any): Coverage { + const metrics: any[] = Array.isArray(doc?.metrics) ? doc.metrics : [] + let unevaluated = 0 + let total = 0 + for (const metric of metrics) { + const criteria: any[] = Array.isArray(metric?.criteria) + ? metric.criteria + : [] + for (const c of criteria) { + total++ + if (c?.status === "unevaluated") unevaluated++ + } + } + return { evaluated: total - unevaluated, unevaluated, total } +} diff --git a/src/lib/scoring.ts b/src/lib/scoring.ts index c21dad4..71f4412 100644 --- a/src/lib/scoring.ts +++ b/src/lib/scoring.ts @@ -1,22 +1,8 @@ -import { CRITERIA_STATUS, getMetricsByTokenId, type Metric } from "@/lib/metrics-data" +import { CRITERIA_STATUS, type Metric } from "@/lib/metrics-data" +import type { MetricScore, TokenScore } from "@/lib/schemas" +import { getTokenById } from "@/lib/token-data" -export interface MetricScore { - metricId: string - metricName: string - passing: number - total: number - percentage: number - evaluated: boolean - reference: boolean -} - -export interface TokenScore { - tokenId: string - passing: number - total: number - percentage: number - metrics: MetricScore[] -} +export type { MetricScore, TokenScore } export type ScoreStatus = "passing" | "warning" | "not-evaluated" @@ -28,6 +14,11 @@ export function getScoreStatus( return percentage >= 75 ? "passing" : "warning" } +/** + * Pure per-metric scoring, used at render time by metric cards. The same + * logic runs at compose time in scripts/compose-data.mjs (the two are pinned + * to each other by the golden round-trip tests). + */ export function getMetricScore(metric: Metric): MetricScore { const reference = metric.tags?.includes("Reference") ?? false const evaluatedCriteria = metric.criteria.filter( @@ -54,20 +45,13 @@ export function getMetricScore(metric: Metric): MetricScore { } } +/** + * Token scores are precomputed at compose time and carried on index rows + * (full per-metric breakdown β€” the dashboard score hover needs it), so this + * works on every page from the index read model alone. + */ export function getTokenOwnershipScore(tokenId: string): TokenScore { - const metrics = getMetricsByTokenId(tokenId) - const metricScores = metrics.map(getMetricScore) - - const scoredMetrics = metricScores.filter((m) => m.evaluated && !m.reference) - const passing = scoredMetrics.reduce((sum, m) => sum + m.passing, 0) - const total = scoredMetrics.reduce((sum, m) => sum + m.total, 0) - const percentage = total > 0 ? (passing / total) * 100 : 0 - - return { - tokenId, - passing, - total, - percentage, - metrics: metricScores, - } + const row = getTokenById(tokenId) + if (row) return row.score + return { tokenId, passing: 0, total: 0, percentage: 0, metrics: [] } } diff --git a/src/lib/server/openapi.ts b/src/lib/server/openapi.ts new file mode 100644 index 0000000..40b56b0 --- /dev/null +++ b/src/lib/server/openapi.ts @@ -0,0 +1,204 @@ +/** + * Machine-readable contract for the public read API. + * + * The OpenAPI 3.1 document is built FROM the vendored Zod read-models so the + * schema can never drift from the shapes the handlers actually serve: the + * component schemas are emitted by zod's native `z.toJSONSchema` (zod v4), + * not hand-duplicated. The handcrafted part is only the API surface that has + * no Zod source β€” paths, the `{data, provenance}` envelope wiring, and the + * response/status metadata. + */ +import { z } from "zod" +import { + apiErrorSchema, + faqSchema, + frameworkDocSchema, + indexSchema, + provenanceSchema, + tokenDocSchema, +} from "@/lib/schemas" + +/** OpenAPI `components.schemas` ids. Kept stable β€” codegen consumers $ref these. */ +const SCHEMA_IDS = { + index: "TokenIndex", + tokenDoc: "TokenDoc", + framework: "FrameworkDoc", + faq: "Faq", + provenance: "Provenance", + apiError: "ApiError", +} as const + +/** `#/components/schemas/` ref to a named component. */ +function ref(id: string): { $ref: string } { + return { $ref: `#/components/schemas/${id}` } +} + +/** + * Emit `components.schemas` from the Zod read-models via zod-native JSON Schema + * conversion. Registering each top-level schema with an id makes zod cross-link + * reused subschemas with `#/components/schemas/` $refs; subschemas that are + * not registered are inlined. We strip the per-schema `$schema`/`$id` dialect + * keys zod stamps on, which OpenAPI 3.1 does not want on component objects. + */ +function buildComponentSchemas(): Record { + const registry = z.registry<{ id: string }>() + registry.add(indexSchema, { id: SCHEMA_IDS.index }) + registry.add(tokenDocSchema, { id: SCHEMA_IDS.tokenDoc }) + registry.add(frameworkDocSchema, { id: SCHEMA_IDS.framework }) + registry.add(faqSchema, { id: SCHEMA_IDS.faq }) + registry.add(provenanceSchema, { id: SCHEMA_IDS.provenance }) + registry.add(apiErrorSchema, { id: SCHEMA_IDS.apiError }) + + const { schemas } = z.toJSONSchema(registry, { + uri: (id) => `#/components/schemas/${id}`, + // OpenAPI 3.1 aligns with JSON Schema 2020-12; keep refs as plain pointers. + target: "draft-2020-12", + }) + + const cleaned: Record = {} + for (const [id, schema] of Object.entries(schemas)) { + const { $schema, $id, ...rest } = schema as Record + cleaned[id] = rest + } + return cleaned +} + +/** The `{ data, provenance }` success envelope wrapping a named data schema. */ +function envelope(dataRef: { $ref: string }): Record { + return { + type: "object", + properties: { + data: dataRef, + provenance: ref(SCHEMA_IDS.provenance), + }, + required: ["data", "provenance"], + additionalProperties: false, + } +} + +/** A 200 response serving the envelope around `dataRef`. */ +function okEnvelope(description: string, dataRef: { $ref: string }) { + return { + "200": { + description, + content: { "application/json": { schema: envelope(dataRef) } }, + }, + } +} + +/** The shared `{ error: { code, message } }` response for a status code. */ +function errorResponse(description: string) { + return { + description, + content: { + "application/json": { schema: ref(SCHEMA_IDS.apiError) }, + }, + } +} + +const NOT_MODIFIED = { + "304": { + description: + "Not Modified β€” the client's `If-None-Match` matched the current `ETag`. No body.", + }, +} as const + +/** + * Build the OpenAPI 3.1 document for the four public GET endpoints plus the + * discovery root. Pure and deterministic for a given build of the schemas. + */ +export function buildOpenApiDocument(): Record { + return { + openapi: "3.1.0", + info: { + title: "Ownership Token Framework API", + version: "v1", + description: + "Structured, evidence-backed analysis of how tokenholder-owned crypto " + + "protocols are. Read-only JSON, no auth. Every response is " + + "`{ data, provenance }`; errors are `{ error: { code, message } }`.", + }, + servers: [{ url: "/api/v1" }], + paths: { + "/": { + get: { + operationId: "getDiscoveryRoot", + summary: + "API discovery root: version, endpoints, schema, and guides.", + responses: { + "200": { + description: "Discovery document.", + content: { "application/json": { schema: { type: "object" } } }, + }, + }, + }, + }, + "/tokens": { + get: { + operationId: "getTokens", + summary: "Index of all analyzed tokens.", + responses: { + ...okEnvelope( + "Token index wrapped in a provenance envelope.", + ref(SCHEMA_IDS.index) + ), + ...NOT_MODIFIED, + }, + }, + }, + "/tokens/{id}": { + get: { + operationId: "getToken", + summary: "Full analysis for one token.", + parameters: [ + { + name: "id", + in: "path", + required: true, + description: "Lowercase token id, e.g. `ldo`.", + schema: { type: "string" }, + }, + ], + responses: { + ...okEnvelope( + "Composed token doc wrapped in a provenance envelope.", + ref(SCHEMA_IDS.tokenDoc) + ), + ...NOT_MODIFIED, + "404": errorResponse("No published token with that id."), + }, + }, + }, + "/framework": { + get: { + operationId: "getFramework", + summary: + "The evaluation framework: metric and criterion definitions.", + responses: { + ...okEnvelope( + "Framework doc wrapped in a provenance envelope.", + ref(SCHEMA_IDS.framework) + ), + ...NOT_MODIFIED, + }, + }, + }, + "/faq": { + get: { + operationId: "getFaq", + summary: "Framework and methodology Q&A.", + responses: { + ...okEnvelope( + "FAQ doc wrapped in a provenance envelope.", + ref(SCHEMA_IDS.faq) + ), + ...NOT_MODIFIED, + }, + }, + }, + }, + components: { + schemas: buildComponentSchemas(), + }, + } +} diff --git a/src/lib/server/published-data.ts b/src/lib/server/published-data.ts new file mode 100644 index 0000000..8c7e1f5 --- /dev/null +++ b/src/lib/server/published-data.ts @@ -0,0 +1,77 @@ +/** + * Published-data source for the canonical API endpoints. + * + * This is the transport seam: today it reads the committed composed read + * models (src/data/generated/); when the publish pipeline lands, a KV-backed + * implementation replaces the internals without any response-shape change β€” + * consumers depend only on this module's interface. + */ +import faqData from "@/data/generated/faq.json" +import frameworkData from "@/data/generated/framework.json" +import indexData from "@/data/generated/index.json" +import manifestData from "@/data/generated/manifest.json" +import type { + FaqTopic, + FrameworkDoc, + IndexRow, + Manifest, + Provenance, + TokenDoc, +} from "@/lib/schemas" + +const manifest = manifestData as Manifest + +const tokenDocModules = import.meta.glob<{ default: TokenDoc }>( + "../../data/generated/tokens/*.json", + { eager: true } +) + +const tokenDocs = new Map( + Object.values(tokenDocModules).map((mod) => [mod.default.id, mod.default]) +) + +/** + * Commit ref resolved from the deployment environment at request time. + * Falls back to "dev" outside CI/deploy contexts so the field is never null. + */ +function resolveCommitRef(): string { + return ( + process.env.VERCEL_GIT_COMMIT_SHA ?? + process.env.CF_PAGES_COMMIT_SHA ?? + process.env.GITHUB_SHA ?? + "dev" + ) +} + +export function getProvenance(): Provenance { + return { + snapshot_id: manifest.snapshot_id, + commit_ref: resolveCommitRef(), + // When the content was last edited (carried in the composed manifest). + last_updated: manifest.last_updated, + // Stamped by the publish pipeline once snapshots are actually published; + // kept distinct from last_updated. + published_at: null, + source: "generated", + } +} + +export function getPublishedIndex(): { tokens: IndexRow[] } { + return indexData as { tokens: IndexRow[] } +} + +export function getPublishedTokenDoc(tokenId: string): TokenDoc | null { + return tokenDocs.get(tokenId.trim().toLowerCase()) ?? null +} + +export function getPublishedFramework(): FrameworkDoc { + return frameworkData as FrameworkDoc +} + +export function getPublishedFaq(): { topics: FaqTopic[] } { + return faqData as { topics: FaqTopic[] } +} + +export function listPublishedTokenIds(): string[] { + return manifest.tokens +} diff --git a/src/lib/server/published-source.ts b/src/lib/server/published-source.ts new file mode 100644 index 0000000..675c3f5 --- /dev/null +++ b/src/lib/server/published-source.ts @@ -0,0 +1,276 @@ +/** + * Runtime published-content source with a committed-data fallback. + * + * This sits in front of published-data.ts (the committed read models). When the + * publish pipeline is enabled via env, it fetches the latest snapshot from the + * private aragon/otf-cms GitHub Release, validates it against the vendored + * schemas, caches it in-process with a short TTL, and serves it. With NO env + * configured (the default, shipped dark) it transparently serves the committed + * data β€” byte-identical to the pre-pipeline behavior, provenance.source + * "generated". + * + * Hard rule: this module NEVER throws to the caller. Any fetch/parse/validate + * failure falls back to committed data and logs a warning. + */ +import { + type FaqTopic, + type FrameworkDoc, + faqSchema, + frameworkDocSchema, + type IndexRow, + indexSchema, + type Manifest, + manifestSchema, + type Provenance, + type TokenDoc, + testimonialsSchema, + tokenDocSchema, +} from "@/lib/schemas" +import { + getPublishedFaq as getCommittedFaq, + getPublishedFramework as getCommittedFramework, + getPublishedIndex as getCommittedIndex, + getProvenance as getCommittedProvenance, + getPublishedTokenDoc as getCommittedTokenDoc, +} from "@/lib/server/published-data" + +// The content repo, overridable so a fork can read its own releases. Defaults +// to the canonical Aragon repo. +const CONTENT_REPO = process.env.OTF_CONTENT_REPO ?? "aragon/otf-cms" +const RELEASE_API_URL = `https://api.github.com/repos/${CONTENT_REPO}/releases/latest` +const SNAPSHOT_ASSET_NAME = "snapshot.json" +const USER_AGENT = "otf-dashboard" +/** How long a validated release bundle is served before we revalidate. */ +const CACHE_TTL_MS = 60_000 +/** + * Whole-operation budget for a release read (lookup + asset). A timeout aborts + * the in-flight fetch, which surfaces as the same fallback as any other failure + * β€” so a slow or hung GitHub can never block an SSR render. + */ +const REQUEST_TIMEOUT_MS = 8_000 + +/** A fully validated snapshot bundle composed from the release asset. */ +type Bundle = { + manifest: Manifest + index: { tokens: IndexRow[] } + tokens: Map + framework: FrameworkDoc + faq: { topics: FaqTopic[] } + /** The release's publication time (ISO) β€” distinct from content last_updated. */ + publishedAt: string | null +} + +/** + * Cross-file integrity: the manifest list, the index rows, and the token docs + * must all cover the SAME ids. Per-part schema validation can't catch a + * shape-valid but incomplete snapshot β€” e.g. an index row whose token doc is + * missing would link the dashboard to a /tokens/{id} that 404s. Throwing here + * is caught by the caller and falls back to committed data. + */ +function assertTokenSetsAgree( + manifest: Manifest, + index: { tokens: IndexRow[] }, + tokens: Map +): void { + const manifestIds = [...manifest.tokens].sort() + const indexIds = index.tokens.map((t) => t.id).sort() + const docIds = [...tokens.keys()].sort() + const agree = + manifestIds.length === indexIds.length && + manifestIds.length === docIds.length && + manifestIds.every((id, i) => id === indexIds[i] && id === docIds[i]) + if (!agree) { + throw new Error( + `snapshot token sets disagree (manifest ${manifestIds.length}, index ${indexIds.length}, docs ${docIds.length})` + ) + } +} + +/** Module-level cache of the last successfully validated release bundle. */ +let cachedBundle: Bundle | null = null +let cachedAtMs = 0 +/** De-dupes concurrent revalidations so a request burst issues one fetch. */ +let inFlight: Promise | null = null + +/** + * The publish pipeline is opt-in. With OTF_PUBLISHED_RELEASE unset (or anything + * other than "true") we serve committed data exactly as before. + * + * TODO(post-migration): this flag is a dark-launch tool, not a permanent + * control β€” once runtime reads are provisioned and trusted, delete it and gate + * on OTF_CONTENT_TOKEN presence instead (no token β†’ committed; correct for + * dev/test/CI). The graceful fallback below stays regardless. + */ +export function isReleaseEnabled(): boolean { + return process.env.OTF_PUBLISHED_RELEASE === "true" +} + +/** Mirrors published-data.ts so committed and release provenance agree. */ +function resolveCommitRef(): string { + return ( + process.env.VERCEL_GIT_COMMIT_SHA ?? + process.env.CF_PAGES_COMMIT_SHA ?? + process.env.GITHUB_SHA ?? + "dev" + ) +} + +/** + * Fetch + validate the latest release snapshot into a Bundle. Returns null on + * ANY failure (network, non-2xx, missing asset, malformed JSON, schema + * violation) after logging a warning β€” the caller then falls back to committed + * data. + */ +async function fetchReleaseBundle(): Promise { + const token = process.env.OTF_CONTENT_TOKEN + try { + // One time budget shared across both fetches below. + const signal = AbortSignal.timeout(REQUEST_TIMEOUT_MS) + const releaseRes = await fetch(RELEASE_API_URL, { + signal, + headers: { + ...(token ? { Authorization: `Bearer ${token}` } : {}), + Accept: "application/vnd.github+json", + "User-Agent": USER_AGENT, + }, + }) + if (!releaseRes.ok) { + console.warn( + `[published-source] release lookup failed: ${releaseRes.status} ${releaseRes.statusText}` + ) + return null + } + const release = (await releaseRes.json()) as { + published_at?: string | null + assets?: Array<{ name?: string; url?: string }> + } + const asset = release.assets?.find((a) => a.name === SNAPSHOT_ASSET_NAME) + if (!asset?.url) { + console.warn( + `[published-source] release has no "${SNAPSHOT_ASSET_NAME}" asset` + ) + return null + } + + const assetRes = await fetch(asset.url, { + signal, + headers: { + ...(token ? { Authorization: `Bearer ${token}` } : {}), + // The GitHub asset API returns the raw bytes only with this Accept. + Accept: "application/octet-stream", + "User-Agent": USER_AGENT, + }, + }) + if (!assetRes.ok) { + console.warn( + `[published-source] asset download failed: ${assetRes.status} ${assetRes.statusText}` + ) + return null + } + + const raw = (await assetRes.json()) as Record + + // Validate each part against the vendored schemas. A throw here is caught + // below and surfaces as a fallback, so a bad publish can never serve + // malformed shapes. + const manifest = manifestSchema.parse(raw.manifest) + const index = indexSchema.parse(raw.index) + const framework = frameworkDocSchema.parse(raw.framework) + const faq = faqSchema.parse(raw.faq) + // Validated for integrity but not served via this seam's endpoints. + testimonialsSchema.parse(raw.testimonials) + + const rawTokens = (raw.tokens ?? {}) as Record + const tokens = new Map() + for (const doc of Object.values(rawTokens)) { + const parsed = tokenDocSchema.parse(doc) + tokens.set(parsed.id, parsed) + } + + assertTokenSetsAgree(manifest, index, tokens) + + return { + manifest, + index, + tokens, + framework, + faq, + publishedAt: release.published_at ?? null, + } + } catch (err) { + console.warn( + `[published-source] falling back to committed data: ${ + err instanceof Error ? err.message : String(err) + }` + ) + return null + } +} + +/** + * Returns the active release bundle, fetching/revalidating as needed. Serves a + * fresh cached bundle immediately; on expiry revalidates and, if that fails, + * keeps serving the stale bundle rather than dropping to committed data. Returns + * null only when the release is disabled or no bundle has ever validated. + */ +async function getActiveBundle(): Promise { + if (!isReleaseEnabled()) { + return null + } + const fresh = cachedBundle && Date.now() - cachedAtMs < CACHE_TTL_MS + if (fresh) { + return cachedBundle + } + // Coalesce concurrent revalidations into one in-flight fetch. + if (!inFlight) { + inFlight = fetchReleaseBundle().finally(() => { + inFlight = null + }) + } + const next = await inFlight + if (next) { + cachedBundle = next + cachedAtMs = Date.now() + return next + } + // Revalidation failed β€” serve the last good bundle if we have one, else the + // caller falls back to committed data. + return cachedBundle +} + +export async function getProvenance(): Promise { + const bundle = await getActiveBundle() + if (!bundle) { + return getCommittedProvenance() + } + return { + snapshot_id: bundle.manifest.snapshot_id, + commit_ref: resolveCommitRef(), + last_updated: bundle.manifest.last_updated, + published_at: bundle.publishedAt, + source: "release", + } +} + +export async function getIndex(): Promise<{ tokens: IndexRow[] }> { + const bundle = await getActiveBundle() + return bundle ? bundle.index : getCommittedIndex() +} + +export async function getTokenDoc(tokenId: string): Promise { + const bundle = await getActiveBundle() + if (!bundle) { + return getCommittedTokenDoc(tokenId) + } + return bundle.tokens.get(tokenId.trim().toLowerCase()) ?? null +} + +export async function getFramework(): Promise { + const bundle = await getActiveBundle() + return bundle ? bundle.framework : getCommittedFramework() +} + +export async function getFaq(): Promise<{ topics: FaqTopic[] }> { + const bundle = await getActiveBundle() + return bundle ? bundle.faq : getCommittedFaq() +} diff --git a/src/lib/server/token-api.ts b/src/lib/server/token-api.ts new file mode 100644 index 0000000..14f0a1b --- /dev/null +++ b/src/lib/server/token-api.ts @@ -0,0 +1,256 @@ +/** + * Handlers for the canonical token JSON API. Kept separate from the route + * files so they are directly unit-testable; routes are thin wrappers. + * + * Response contract (consumed by the app, future APP-796 agents, and + * external partners): + * { data: , provenance: { snapshot_id, commit_ref, last_updated, published_at, source } } + * + * NOTE: the route parameter is the token `id` (lowercase, e.g. "ldo") β€” + * matching generated/tokens/.json and the existing /tokens/$tokenId page + * route. APP-796's "{symbol}" wording resolves to this id (symbols lowercase + * to ids for all current tokens). + */ +import type { Provenance } from "@/lib/schemas" +import { buildOpenApiDocument } from "@/lib/server/openapi" +import { + getFaq, + getFramework, + getIndex, + getProvenance, + getTokenDoc, + isReleaseEnabled, +} from "@/lib/server/published-source" + +// Cache posture depends on the data source. +// - Committed / build-from-ref data is immutable for the life of the deploy, so +// we cache hard at the edge: after the first hit per edge node the CDN serves +// everything and the function is never invoked β€” a flood is absorbed by the +// CDN, the primary DoS posture (see .tempor/docs/operations/api-hardening.md). +// - In release mode the data can change WITHOUT a deploy (a new GitHub Release), +// so a day-long edge cache would serve a stale snapshot. We cap it near the +// source's revalidation window so a new snapshot propagates in ~a minute, +// still mostly CDN-absorbed. (Eventual upgrade: a publishβ†’app purge webhook β€” +// instant propagation while keeping the long cache.) +const CACHE_OK = isReleaseEnabled() + ? "public, max-age=30, s-maxage=60, stale-while-revalidate=60" + : "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" +// Misses (unknown ids) are cached briefly so a repeated bogus id can't hammer +// the function; short because an id can become valid on the next deploy. +const CACHE_MISS = "public, max-age=30, s-maxage=60" + +function baseHeaders(cacheControl: string): Record { + return { + "Content-Type": "application/json", + "Cache-Control": cacheControl, + // Public, read-only data with no credentials β€” a wildcard origin is safe + // and keeps CORS from being a footgun for consumers. + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + // Never let a client sniff the response into another content type. + "X-Content-Type-Options": "nosniff", + } +} + +function jsonResponse( + body: unknown, + status = 200, + cacheControl = CACHE_OK +): Response { + return new Response(JSON.stringify(body), { + status, + headers: baseHeaders(cacheControl), + }) +} + +/** + * A strong validator over the snapshot identity. Every 200 body is fully + * determined by the snapshot it was composed from, so the snapshot_id is a + * sound ETag: identical snapshot β‡’ byte-identical body. Quoted per RFC 9110. + */ +function snapshotEtag(provenance: Provenance): string { + return `"${provenance.snapshot_id}"` +} + +/** + * Does the request's `If-None-Match` already hold this `etag`? Handles the + * comma-separated list form and the `*` wildcard. Conservative: any parse + * oddity simply yields false (we serve the full body), never a wrong 304. + */ +function ifNoneMatchSatisfied( + request: Request | undefined, + etag: string +): boolean { + const header = request?.headers.get("If-None-Match") + if (!header) { + return false + } + if (header.trim() === "*") { + return true + } + return ( + header + .split(",") + .map((tag) => tag.trim()) + // Compare ignoring a weak prefix; our validator is strong. + .map((tag) => (tag.startsWith("W/") ? tag.slice(2) : tag)) + .includes(etag) + ) +} + +/** + * Build a 200 envelope response carrying an `ETag` derived from the snapshot, + * or a bodyless `304 Not Modified` when the client's `If-None-Match` already + * matches. Cache-Control still rides along on the 304 so the validator's + * freshness lifetime is refreshed. + */ +function envelopeResponse( + data: unknown, + provenance: Provenance, + request?: Request, + cacheControl = CACHE_OK +): Response { + const etag = snapshotEtag(provenance) + if (ifNoneMatchSatisfied(request, etag)) { + return new Response(null, { + status: 304, + headers: { ...baseHeaders(cacheControl), ETag: etag }, + }) + } + return new Response(JSON.stringify({ data, provenance }), { + status: 200, + headers: { ...baseHeaders(cacheControl), ETag: etag }, + }) +} + +/** Cheap CORS preflight / method probe β€” no body, cacheable. */ +export function handleOptions(): Response { + return new Response(null, { + status: 204, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Max-Age": "86400", + "Cache-Control": CACHE_OK, + }, + }) +} + +/** + * Reject non-GET methods with a cheap 405 instead of letting them fall through + * to the SSR shell β€” an uncacheable HTML render is a far more expensive (and + * cache-bypassing) thing to serve a flood than a one-line 405. + */ +export function handleMethodNotAllowed(): Response { + return new Response( + JSON.stringify({ + error: { code: "METHOD_NOT_ALLOWED", message: "Only GET is supported." }, + }), + { + status: 405, + headers: { + "Content-Type": "application/json", + Allow: "GET, OPTIONS", + "Cache-Control": CACHE_MISS, + }, + } + ) +} + +/** GET /api/v1/tokens β€” the published index (discovery + cross-token queries). */ +export async function handleGetTokens(request?: Request): Promise { + const [data, provenance] = await Promise.all([getIndex(), getProvenance()]) + return envelopeResponse(data, provenance, request) +} + +/** GET /api/v1/framework β€” canonical metric/criteria definitions + anchors. */ +export async function handleGetFramework(request?: Request): Promise { + const [data, provenance] = await Promise.all([ + getFramework(), + getProvenance(), + ]) + return envelopeResponse(data, provenance, request) +} + +/** GET /api/v1/faq β€” published framework/methodology Q&A. */ +export async function handleGetFaq(request?: Request): Promise { + const [data, provenance] = await Promise.all([getFaq(), getProvenance()]) + return envelopeResponse(data, provenance, request) +} + +/** GET /api/v1/tokens/{id} β€” one composed token doc (the per-token reusable unit). */ +export async function handleGetToken( + tokenId: string, + request?: Request +): Promise { + const doc = await getTokenDoc(tokenId) + if (!doc) { + return jsonResponse( + { + error: { + code: "TOKEN_NOT_FOUND", + message: `No published token with id "${tokenId.trim().toLowerCase()}"`, + }, + }, + 404, + CACHE_MISS + ) + } + return envelopeResponse(doc, await getProvenance(), request) +} + +/** + * GET /api/v1/openapi.json β€” the machine-readable contract for this API. + * + * Built from the vendored Zod read-models (see openapi.ts), so it tracks the + * served shapes automatically. The document is content-versioned by the active + * snapshot too, so it participates in the same ETag/conditional-GET flow. + */ +export async function handleGetOpenApi(request?: Request): Promise { + const provenance = await getProvenance() + const doc = buildOpenApiDocument() + const etag = snapshotEtag(provenance) + if (ifNoneMatchSatisfied(request, etag)) { + return new Response(null, { + status: 304, + headers: { ...baseHeaders(CACHE_OK), ETag: etag }, + }) + } + return new Response(JSON.stringify(doc), { + status: 200, + headers: { ...baseHeaders(CACHE_OK), ETag: etag }, + }) +} + +/** + * GET /api/v1 (served as /api/v1/index.json) β€” discovery root. Points typed and + * programmatic consumers at the formal schema and the human/agent guides, and + * carries provenance like every other response. + */ +export async function handleGetDiscovery(request?: Request): Promise { + const provenance = await getProvenance() + const etag = snapshotEtag(provenance) + if (ifNoneMatchSatisfied(request, etag)) { + return new Response(null, { + status: 304, + headers: { ...baseHeaders(CACHE_OK), ETag: etag }, + }) + } + const body = { + name: "Ownership Token Framework API", + version: "v1", + endpoints: [ + "/api/v1/tokens", + "/api/v1/tokens/{id}", + "/api/v1/framework", + "/api/v1/faq", + ], + schema: "/api/v1/openapi.json", + guides: ["/llms.txt", "/agent-guide.md"], + provenance, + } + return new Response(JSON.stringify(body), { + status: 200, + headers: { ...baseHeaders(CACHE_OK), ETag: etag }, + }) +} diff --git a/src/lib/testimonials-data.ts b/src/lib/testimonials-data.ts index cbe9ad2..107534f 100644 --- a/src/lib/testimonials-data.ts +++ b/src/lib/testimonials-data.ts @@ -1,17 +1,6 @@ -import testimonialsData from "@/data/testimonials.json" +import testimonialsData from "@/data/generated/testimonials.json" +import type { Testimonial, TestimonialsContent } from "@/lib/schemas" -export interface Testimonial { - id: string - name: string - organization: string - avatar: string - url: string - quote: string -} - -interface TestimonialsContent { - title: string - testimonials: Testimonial[] -} +export type { Testimonial } export const testimonialsContent = testimonialsData as TestimonialsContent diff --git a/src/lib/token-data-dynamic.ts b/src/lib/token-data-dynamic.ts deleted file mode 100644 index 5eaff5f..0000000 --- a/src/lib/token-data-dynamic.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Token Data with Dynamic GitHub Fetching - * - * This is an example of how to refactor token-data.ts to use the GitHub fetcher. - * Replace the existing token-data.ts with this implementation once data is moved to GitHub. - */ - -// Import local data as fallback for development -import tokensDataLocal from "@/data/tokens.json" -import { fetchGitHubData } from "@/lib/data-fetcher" - -export type Token = (typeof tokensDataLocal.tokens)[number] - -interface TokensResponse { - tokens: Token[] -} - -let cachedTokens: Token[] | null = null - -/** - * Fetch tokens from GitHub or use local fallback - */ -async function fetchTokens(): Promise { - // If already cached in memory, return immediately - if (cachedTokens) return cachedTokens - - // Try to fetch from GitHub - try { - const data = await fetchGitHubData("tokens") - cachedTokens = data.tokens - return cachedTokens - } catch (error) { - console.warn( - "Failed to fetch tokens from GitHub, using local fallback", - error - ) - // Fallback to local data - cachedTokens = tokensDataLocal.tokens as Token[] - return cachedTokens - } -} - -function normalizeTokenSymbol(value: string): string { - return value.trim().toUpperCase() -} - -/** - * Get all tokens with optional symbol filter from env var - */ -export async function getTokens(): Promise { - const allTokens = await fetchTokens() - - // Apply env var filter if present - const envSymbolFilter = normalizeTokenSymbol( - import.meta.env.VITE_TOKEN_SYMBOL ?? "" - ) - - if (!envSymbolFilter) return allTokens - - const matchingTokens = allTokens.filter( - (token) => normalizeTokenSymbol(token.symbol) === envSymbolFilter - ) - - return matchingTokens.length > 0 ? matchingTokens : allTokens -} - -/** - * Get a single token by ID - */ -export async function getTokenById(tokenId: string): Promise { - const tokens = await getTokens() - const normalizedId = tokenId.trim().toLowerCase() - return tokens.find((token) => token.id.toLowerCase() === normalizedId) ?? null -} - -/** - * Synchronous versions for backwards compatibility - * Note: These use the local fallback data and should be deprecated - */ -export function getTokensSync(): Token[] { - return tokensDataLocal.tokens as Token[] -} - -export function getTokenByIdSync(tokenId: string): Token | null { - const normalizedId = tokenId.trim().toLowerCase() - return ( - (tokensDataLocal.tokens as Token[]).find( - (token) => token.id.toLowerCase() === normalizedId - ) ?? null - ) -} diff --git a/src/lib/token-data.ts b/src/lib/token-data.ts index 857327d..042fb8b 100644 --- a/src/lib/token-data.ts +++ b/src/lib/token-data.ts @@ -1,35 +1,34 @@ -import tokensData from "@/data/tokens.json" - -export type Token = (typeof tokensData.tokens)[number] - -const rawTokens = tokensData.tokens as Token[] - -function normalizeTokenSymbol(value: string): string { - return value.trim().toUpperCase() -} - -const envSymbolFilter = normalizeTokenSymbol( - import.meta.env.VITE_TOKEN_SYMBOL ?? "" -) - -const matchingTokens = envSymbolFilter - ? rawTokens.filter( - (token) => normalizeTokenSymbol(token.symbol) === envSymbolFilter +import { publishedIndexQuery } from "@/lib/published-queries" +import { queryClient } from "@/lib/query-client" +import type { IndexRow } from "@/lib/schemas" + +export type Token = IndexRow + +/** + * Synchronous read over the hydrated query cache. Route loaders are + * responsible for ensuring the published index before render (the root + * route loader does this for every page). A cache miss is a programming + * error and fails loudly by design β€” no silent fallbacks (ADR 0002 spirit). + */ +function readPublishedIndex(): { tokens: Token[] } { + const data = queryClient.getQueryData(publishedIndexQuery.queryKey) + if (!data) { + throw new Error( + "Published token index is not in the query cache β€” a route loader must ensure publishedIndexQuery before render" ) - : [] - -const filteredTokens = matchingTokens.length > 0 ? matchingTokens : rawTokens + } + return data +} function getTokens(): Token[] { - return filteredTokens + return readPublishedIndex().tokens } function getTokenById(tokenId: string): Token | null { const normalizedId = tokenId.trim().toLowerCase() return ( - filteredTokens.find((token) => token.id.toLowerCase() === normalizedId) ?? - null + getTokens().find((token) => token.id.toLowerCase() === normalizedId) ?? null ) } -export { envSymbolFilter, getTokenById, getTokens } +export { getTokenById, getTokens } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 172de8a..c8f2b27 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -9,6 +9,17 @@ export function copyToClipboard(value: string) { navigator.clipboard.writeText(value) } +/** + * A field is a placeholder β€” not real, renderable content β€” when it is absent, + * empty, or the "TK" deferral sentinel. Previews build from partial (WIP) + * tokens, so composed docs reaching the UI may carry "TK" in url/text fields; + * the UI must treat it exactly like empty (never an , , or + * literal text). See src/lib/schemas/common.ts (TK) and readiness.ts. + */ +export function isPlaceholder(value: unknown): boolean { + return value == null || value === "" || value === "TK" +} + export function truncateAddress(address: string, start = 6, end = 4) { if (address.includes("...")) { return address diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 94bc6f0..082c8e9 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -12,6 +12,12 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as FaqRouteImport } from './routes/faq' import { Route as IndexRouteImport } from './routes/index' import { Route as TokensTokenIdRouteImport } from './routes/tokens/$tokenId' +import { Route as ApiV1TokensRouteImport } from './routes/api.v1.tokens' +import { Route as ApiV1OpenapiDotjsonRouteImport } from './routes/api.v1.openapi[.]json' +import { Route as ApiV1IndexDotjsonRouteImport } from './routes/api.v1.index[.]json' +import { Route as ApiV1FrameworkRouteImport } from './routes/api.v1.framework' +import { Route as ApiV1FaqRouteImport } from './routes/api.v1.faq' +import { Route as ApiV1TokensTokenIdRouteImport } from './routes/api.v1.tokens.$tokenId' const FaqRoute = FaqRouteImport.update({ id: '/faq', @@ -28,35 +34,116 @@ const TokensTokenIdRoute = TokensTokenIdRouteImport.update({ path: '/tokens/$tokenId', getParentRoute: () => rootRouteImport, } as any) +const ApiV1TokensRoute = ApiV1TokensRouteImport.update({ + id: '/api/v1/tokens', + path: '/api/v1/tokens', + getParentRoute: () => rootRouteImport, +} as any) +const ApiV1OpenapiDotjsonRoute = ApiV1OpenapiDotjsonRouteImport.update({ + id: '/api/v1/openapi.json', + path: '/api/v1/openapi.json', + getParentRoute: () => rootRouteImport, +} as any) +const ApiV1IndexDotjsonRoute = ApiV1IndexDotjsonRouteImport.update({ + id: '/api/v1/index.json', + path: '/api/v1/index.json', + getParentRoute: () => rootRouteImport, +} as any) +const ApiV1FrameworkRoute = ApiV1FrameworkRouteImport.update({ + id: '/api/v1/framework', + path: '/api/v1/framework', + getParentRoute: () => rootRouteImport, +} as any) +const ApiV1FaqRoute = ApiV1FaqRouteImport.update({ + id: '/api/v1/faq', + path: '/api/v1/faq', + getParentRoute: () => rootRouteImport, +} as any) +const ApiV1TokensTokenIdRoute = ApiV1TokensTokenIdRouteImport.update({ + id: '/$tokenId', + path: '/$tokenId', + getParentRoute: () => ApiV1TokensRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute '/faq': typeof FaqRoute '/tokens/$tokenId': typeof TokensTokenIdRoute + '/api/v1/faq': typeof ApiV1FaqRoute + '/api/v1/framework': typeof ApiV1FrameworkRoute + '/api/v1/index.json': typeof ApiV1IndexDotjsonRoute + '/api/v1/openapi.json': typeof ApiV1OpenapiDotjsonRoute + '/api/v1/tokens': typeof ApiV1TokensRouteWithChildren + '/api/v1/tokens/$tokenId': typeof ApiV1TokensTokenIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute '/faq': typeof FaqRoute '/tokens/$tokenId': typeof TokensTokenIdRoute + '/api/v1/faq': typeof ApiV1FaqRoute + '/api/v1/framework': typeof ApiV1FrameworkRoute + '/api/v1/index.json': typeof ApiV1IndexDotjsonRoute + '/api/v1/openapi.json': typeof ApiV1OpenapiDotjsonRoute + '/api/v1/tokens': typeof ApiV1TokensRouteWithChildren + '/api/v1/tokens/$tokenId': typeof ApiV1TokensTokenIdRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/faq': typeof FaqRoute '/tokens/$tokenId': typeof TokensTokenIdRoute + '/api/v1/faq': typeof ApiV1FaqRoute + '/api/v1/framework': typeof ApiV1FrameworkRoute + '/api/v1/index.json': typeof ApiV1IndexDotjsonRoute + '/api/v1/openapi.json': typeof ApiV1OpenapiDotjsonRoute + '/api/v1/tokens': typeof ApiV1TokensRouteWithChildren + '/api/v1/tokens/$tokenId': typeof ApiV1TokensTokenIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/faq' | '/tokens/$tokenId' + fullPaths: + | '/' + | '/faq' + | '/tokens/$tokenId' + | '/api/v1/faq' + | '/api/v1/framework' + | '/api/v1/index.json' + | '/api/v1/openapi.json' + | '/api/v1/tokens' + | '/api/v1/tokens/$tokenId' fileRoutesByTo: FileRoutesByTo - to: '/' | '/faq' | '/tokens/$tokenId' - id: '__root__' | '/' | '/faq' | '/tokens/$tokenId' + to: + | '/' + | '/faq' + | '/tokens/$tokenId' + | '/api/v1/faq' + | '/api/v1/framework' + | '/api/v1/index.json' + | '/api/v1/openapi.json' + | '/api/v1/tokens' + | '/api/v1/tokens/$tokenId' + id: + | '__root__' + | '/' + | '/faq' + | '/tokens/$tokenId' + | '/api/v1/faq' + | '/api/v1/framework' + | '/api/v1/index.json' + | '/api/v1/openapi.json' + | '/api/v1/tokens' + | '/api/v1/tokens/$tokenId' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute FaqRoute: typeof FaqRoute TokensTokenIdRoute: typeof TokensTokenIdRoute + ApiV1FaqRoute: typeof ApiV1FaqRoute + ApiV1FrameworkRoute: typeof ApiV1FrameworkRoute + ApiV1IndexDotjsonRoute: typeof ApiV1IndexDotjsonRoute + ApiV1OpenapiDotjsonRoute: typeof ApiV1OpenapiDotjsonRoute + ApiV1TokensRoute: typeof ApiV1TokensRouteWithChildren } declare module '@tanstack/react-router' { @@ -82,13 +169,72 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof TokensTokenIdRouteImport parentRoute: typeof rootRouteImport } + '/api/v1/tokens': { + id: '/api/v1/tokens' + path: '/api/v1/tokens' + fullPath: '/api/v1/tokens' + preLoaderRoute: typeof ApiV1TokensRouteImport + parentRoute: typeof rootRouteImport + } + '/api/v1/openapi.json': { + id: '/api/v1/openapi.json' + path: '/api/v1/openapi.json' + fullPath: '/api/v1/openapi.json' + preLoaderRoute: typeof ApiV1OpenapiDotjsonRouteImport + parentRoute: typeof rootRouteImport + } + '/api/v1/index.json': { + id: '/api/v1/index.json' + path: '/api/v1/index.json' + fullPath: '/api/v1/index.json' + preLoaderRoute: typeof ApiV1IndexDotjsonRouteImport + parentRoute: typeof rootRouteImport + } + '/api/v1/framework': { + id: '/api/v1/framework' + path: '/api/v1/framework' + fullPath: '/api/v1/framework' + preLoaderRoute: typeof ApiV1FrameworkRouteImport + parentRoute: typeof rootRouteImport + } + '/api/v1/faq': { + id: '/api/v1/faq' + path: '/api/v1/faq' + fullPath: '/api/v1/faq' + preLoaderRoute: typeof ApiV1FaqRouteImport + parentRoute: typeof rootRouteImport + } + '/api/v1/tokens/$tokenId': { + id: '/api/v1/tokens/$tokenId' + path: '/$tokenId' + fullPath: '/api/v1/tokens/$tokenId' + preLoaderRoute: typeof ApiV1TokensTokenIdRouteImport + parentRoute: typeof ApiV1TokensRoute + } } } +interface ApiV1TokensRouteChildren { + ApiV1TokensTokenIdRoute: typeof ApiV1TokensTokenIdRoute +} + +const ApiV1TokensRouteChildren: ApiV1TokensRouteChildren = { + ApiV1TokensTokenIdRoute: ApiV1TokensTokenIdRoute, +} + +const ApiV1TokensRouteWithChildren = ApiV1TokensRoute._addFileChildren( + ApiV1TokensRouteChildren, +) + const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, FaqRoute: FaqRoute, TokensTokenIdRoute: TokensTokenIdRoute, + ApiV1FaqRoute: ApiV1FaqRoute, + ApiV1FrameworkRoute: ApiV1FrameworkRoute, + ApiV1IndexDotjsonRoute: ApiV1IndexDotjsonRoute, + ApiV1OpenapiDotjsonRoute: ApiV1OpenapiDotjsonRoute, + ApiV1TokensRoute: ApiV1TokensRouteWithChildren, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/src/router.tsx b/src/router.tsx index 8c42ff2..d4d224b 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,15 +1,35 @@ import { createRouter } from "@tanstack/react-router" +import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query" +import { queryClient } from "./lib/query-client" // Import the generated route tree import { routeTree } from "./routeTree.gen" -// Create a new router instance -export const router = createRouter({ - routeTree, - scrollRestoration: true, - defaultPreloadStaleTime: 0, - trailingSlash: "preserve", -}) +/** + * Router factory β€” TanStack Start calls this per SSR request (and once on the + * client). The router MUST be fresh per request: SSR streaming state lives on + * the router instance, and a shared router carries request A's consumed + * stream into request B ("ReadableStream is locked"). + * + * The QueryClient stays a shared singleton (src/lib/query-client.ts): the + * published data it caches is immutable per deployment snapshot, so + * cross-request sharing is correct β€” and the data libs read it synchronously. + */ +export function getRouter() { + const router = createRouter({ + routeTree, + scrollRestoration: true, + defaultPreloadStaleTime: 0, + trailingSlash: "preserve", + }) -// Legacy function export for backward compatibility -export const getRouter = () => router + // Dehydrates the query cache into the SSR stream and hydrates it on the + // client, so loader-ensured published data is synchronously readable on + // first client render. Also wraps the app in a QueryClientProvider. + setupRouterSsrQueryIntegration({ + router, + queryClient, + }) + + return router +} diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 54f4b38..d73f797 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,5 +1,4 @@ import { TanStackDevtools } from "@tanstack/react-devtools" -import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { createRootRoute, HeadContent, @@ -14,9 +13,24 @@ import { SiteFooter } from "@/components/site-footer" import { SiteHeader } from "@/components/site-header" import { GA_MEASUREMENT_ID } from "@/lib/analytics" import { generateOpenGraphMetadata } from "@/lib/metadata" +import { + publishedFrameworkQuery, + publishedIndexQuery, + readPublished, +} from "@/lib/published-queries" +import { queryClient } from "@/lib/query-client" import appCss from "../styles.css?url" export const Route = createRootRoute({ + // Every page reads the published index (header search, dashboard) and the + // framework doc (header link, metric cards) synchronously from the query + // cache β€” ensure them before render, on server and client alike. + loader: async () => { + await Promise.all([ + readPublished(queryClient, publishedIndexQuery), + readPublished(queryClient, publishedFrameworkQuery), + ]) + }, head: () => ({ meta: [ { @@ -40,19 +54,15 @@ export const Route = createRootRoute({ notFoundComponent: () => , }) -const queryClient = new QueryClient() - function RootComponent() { return ( - -
- - - - - -
-
+
+ + + + + +
) } diff --git a/src/routes/api.market-data.ts b/src/routes/api.market-data.ts index 2e4b662..1d949d6 100644 --- a/src/routes/api.market-data.ts +++ b/src/routes/api.market-data.ts @@ -1,5 +1,5 @@ import { createServerFn } from "@tanstack/react-start" -import { type MarketData, fetchMarketData } from "@/lib/coingecko" +import { fetchMarketData, type MarketData } from "@/lib/coingecko" type MarketDataInput = { coingeckoIds: string[] diff --git a/src/routes/api.v1.faq.ts b/src/routes/api.v1.faq.ts new file mode 100644 index 0000000..c04aba6 --- /dev/null +++ b/src/routes/api.v1.faq.ts @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router" +import { + handleGetFaq, + handleMethodNotAllowed, + handleOptions, +} from "@/lib/server/token-api" + +export const Route = createFileRoute("/api/v1/faq")({ + server: { + handlers: { + GET: ({ request }) => handleGetFaq(request), + OPTIONS: () => handleOptions(), + POST: () => handleMethodNotAllowed(), + PUT: () => handleMethodNotAllowed(), + PATCH: () => handleMethodNotAllowed(), + DELETE: () => handleMethodNotAllowed(), + }, + }, +}) diff --git a/src/routes/api.v1.framework.ts b/src/routes/api.v1.framework.ts new file mode 100644 index 0000000..6cc1b39 --- /dev/null +++ b/src/routes/api.v1.framework.ts @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router" +import { + handleGetFramework, + handleMethodNotAllowed, + handleOptions, +} from "@/lib/server/token-api" + +export const Route = createFileRoute("/api/v1/framework")({ + server: { + handlers: { + GET: ({ request }) => handleGetFramework(request), + OPTIONS: () => handleOptions(), + POST: () => handleMethodNotAllowed(), + PUT: () => handleMethodNotAllowed(), + PATCH: () => handleMethodNotAllowed(), + DELETE: () => handleMethodNotAllowed(), + }, + }, +}) diff --git a/src/routes/api.v1.index[.]json.ts b/src/routes/api.v1.index[.]json.ts new file mode 100644 index 0000000..052b1eb --- /dev/null +++ b/src/routes/api.v1.index[.]json.ts @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router" +import { + handleGetDiscovery, + handleMethodNotAllowed, + handleOptions, +} from "@/lib/server/token-api" + +export const Route = createFileRoute("/api/v1/index.json")({ + server: { + handlers: { + GET: ({ request }) => handleGetDiscovery(request), + OPTIONS: () => handleOptions(), + POST: () => handleMethodNotAllowed(), + PUT: () => handleMethodNotAllowed(), + PATCH: () => handleMethodNotAllowed(), + DELETE: () => handleMethodNotAllowed(), + }, + }, +}) diff --git a/src/routes/api.v1.openapi[.]json.ts b/src/routes/api.v1.openapi[.]json.ts new file mode 100644 index 0000000..cccf312 --- /dev/null +++ b/src/routes/api.v1.openapi[.]json.ts @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router" +import { + handleGetOpenApi, + handleMethodNotAllowed, + handleOptions, +} from "@/lib/server/token-api" + +export const Route = createFileRoute("/api/v1/openapi.json")({ + server: { + handlers: { + GET: ({ request }) => handleGetOpenApi(request), + OPTIONS: () => handleOptions(), + POST: () => handleMethodNotAllowed(), + PUT: () => handleMethodNotAllowed(), + PATCH: () => handleMethodNotAllowed(), + DELETE: () => handleMethodNotAllowed(), + }, + }, +}) diff --git a/src/routes/api.v1.tokens.$tokenId.ts b/src/routes/api.v1.tokens.$tokenId.ts new file mode 100644 index 0000000..ecf3cdb --- /dev/null +++ b/src/routes/api.v1.tokens.$tokenId.ts @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router" +import { + handleGetToken, + handleMethodNotAllowed, + handleOptions, +} from "@/lib/server/token-api" + +export const Route = createFileRoute("/api/v1/tokens/$tokenId")({ + server: { + handlers: { + GET: ({ params, request }) => handleGetToken(params.tokenId, request), + OPTIONS: () => handleOptions(), + POST: () => handleMethodNotAllowed(), + PUT: () => handleMethodNotAllowed(), + PATCH: () => handleMethodNotAllowed(), + DELETE: () => handleMethodNotAllowed(), + }, + }, +}) diff --git a/src/routes/api.v1.tokens.ts b/src/routes/api.v1.tokens.ts new file mode 100644 index 0000000..66c8162 --- /dev/null +++ b/src/routes/api.v1.tokens.ts @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router" +import { + handleGetTokens, + handleMethodNotAllowed, + handleOptions, +} from "@/lib/server/token-api" + +export const Route = createFileRoute("/api/v1/tokens")({ + server: { + handlers: { + GET: ({ request }) => handleGetTokens(request), + OPTIONS: () => handleOptions(), + POST: () => handleMethodNotAllowed(), + PUT: () => handleMethodNotAllowed(), + PATCH: () => handleMethodNotAllowed(), + DELETE: () => handleMethodNotAllowed(), + }, + }, +}) diff --git a/src/routes/tokens/$tokenId.tsx b/src/routes/tokens/$tokenId.tsx index d9f3825..5e0302e 100644 --- a/src/routes/tokens/$tokenId.tsx +++ b/src/routes/tokens/$tokenId.tsx @@ -1,9 +1,16 @@ import { createFileRoute } from "@tanstack/react-router" import TokenDetail from "@/components/token-detail" import { generateOpenGraphMetadata } from "@/lib/metadata" +import { publishedTokenDocQuery, readPublished } from "@/lib/published-queries" +import { queryClient } from "@/lib/query-client" import { getTokenById } from "@/lib/token-data" export const Route = createFileRoute("/tokens/$tokenId")({ + // The detail page reads the full composed token doc (metrics, criteria, + // evidence) synchronously from the query cache β€” ensure it before render. + loader: async ({ params }) => { + await readPublished(queryClient, publishedTokenDocQuery(params.tokenId)) + }, component: TokenDetailPage, head: ({ params }) => { const token = getTokenById(params.tokenId) diff --git a/tests/api-endpoints.test.ts b/tests/api-endpoints.test.ts new file mode 100644 index 0000000..20a43de --- /dev/null +++ b/tests/api-endpoints.test.ts @@ -0,0 +1,272 @@ +/** + * Canonical token API handlers: envelope contract, payload parity with the + * generated read models, and structured 404s. + */ +import { readFileSync } from "node:fs" +import { join } from "node:path" +import { describe, expect, it } from "vitest" +import { + apiErrorSchema, + faqSchema, + frameworkDocSchema, + indexSchema, + provenanceSchema, + tokenDocSchema, +} from "@/lib/schemas" +import { + handleGetDiscovery, + handleGetFaq, + handleGetFramework, + handleGetOpenApi, + handleGetToken, + handleGetTokens, +} from "@/lib/server/token-api" + +const generated = join(__dirname, "..", "src", "data", "generated") +const readJson = (p: string) => JSON.parse(readFileSync(p, "utf8")) + +const TOKEN_IDS = readJson(join(generated, "manifest.json")).tokens as string[] + +async function body(res: Response) { + return JSON.parse(await res.text()) +} + +describe("GET /api/tokens", () => { + it("returns the published index wrapped in a provenance envelope", async () => { + const res = await handleGetTokens() + expect(res.status).toBe(200) + expect(res.headers.get("Content-Type")).toBe("application/json") + + const payload = await body(res) + expect(() => provenanceSchema.parse(payload.provenance)).not.toThrow() + expect(() => indexSchema.parse(payload.data)).not.toThrow() + }) + + it("data payload is identical to generated/index.json", async () => { + const payload = await body(await handleGetTokens()) + expect(payload.data).toEqual(readJson(join(generated, "index.json"))) + }) + + it("envelope: commit_ref non-null, published_at null pre-pipeline, snapshot_id matches manifest", async () => { + const { provenance } = await body(await handleGetTokens()) + expect(provenance.commit_ref).toBeTruthy() + expect(provenance.published_at).toBeNull() + expect(provenance.source).toBe("generated") + expect(provenance.snapshot_id).toBe( + readJson(join(generated, "manifest.json")).snapshot_id + ) + }) +}) + +describe("GET /api/framework", () => { + it("returns the framework doc with provenance, identical to generated", async () => { + const res = await handleGetFramework() + expect(res.status).toBe(200) + const payload = await body(res) + expect(() => frameworkDocSchema.parse(payload.data)).not.toThrow() + expect(() => provenanceSchema.parse(payload.provenance)).not.toThrow() + expect(payload.data).toEqual(readJson(join(generated, "framework.json"))) + }) +}) + +describe("GET /api/v1/faq", () => { + it("returns the faq doc with provenance, identical to generated", async () => { + const res = await handleGetFaq() + expect(res.status).toBe(200) + const payload = await body(res) + expect(() => faqSchema.parse(payload.data)).not.toThrow() + expect(() => provenanceSchema.parse(payload.provenance)).not.toThrow() + expect(payload.data).toEqual(readJson(join(generated, "faq.json"))) + }) +}) + +describe("GET /api/tokens/{id}", () => { + it("returns every published token doc identical to its generated file", async () => { + for (const id of TOKEN_IDS) { + const res = await handleGetToken(id) + expect(res.status, id).toBe(200) + const payload = await body(res) + expect(() => tokenDocSchema.parse(payload.data), id).not.toThrow() + expect(payload.data, id).toEqual( + readJson(join(generated, "tokens", `${id}.json`)) + ) + expect(() => provenanceSchema.parse(payload.provenance), id).not.toThrow() + } + }) + + it("normalizes id casing and whitespace", async () => { + const res = await handleGetToken(" LDO ") + expect(res.status).toBe(200) + const payload = await body(res) + expect(payload.data.id).toBe("ldo") + }) + + it.each(["doge", "not-a-token"])( + "returns structured 404 for unknown id %s", + async (id) => { + const res = await handleGetToken(id) + expect(res.status).toBe(404) + const payload = await body(res) + expect(() => apiErrorSchema.parse(payload)).not.toThrow() + expect(payload.error.code).toBe("TOKEN_NOT_FOUND") + } + ) +}) + +const snapshotId = readJson(join(generated, "manifest.json")) + .snapshot_id as string +const currentEtag = `"${snapshotId}"` + +describe("ETag / conditional GET", () => { + it("stamps a snapshot-derived ETag on every 200 envelope response", async () => { + for (const res of [ + await handleGetTokens(), + await handleGetFramework(), + await handleGetFaq(), + await handleGetToken("ldo"), + ]) { + expect(res.status).toBe(200) + expect(res.headers.get("ETag")).toBe(currentEtag) + } + }) + + it("returns 304 with no body when If-None-Match matches the ETag", async () => { + const req = new Request("https://x/api/v1/tokens", { + headers: { "If-None-Match": currentEtag }, + }) + const res = await handleGetTokens(req) + expect(res.status).toBe(304) + expect(res.headers.get("ETag")).toBe(currentEtag) + expect(await res.text()).toBe("") + }) + + it("honors the comma-separated and weak forms of If-None-Match", async () => { + const req = new Request("https://x/api/v1/framework", { + headers: { "If-None-Match": `"stale-x", W/${currentEtag}` }, + }) + expect((await handleGetFramework(req)).status).toBe(304) + }) + + it("honors the * wildcard", async () => { + const req = new Request("https://x/api/v1/faq", { + headers: { "If-None-Match": "*" }, + }) + expect((await handleGetFaq(req)).status).toBe(304) + }) + + it("serves the full 200 body when If-None-Match does not match", async () => { + const req = new Request("https://x/api/v1/tokens", { + headers: { "If-None-Match": '"some-other-snapshot"' }, + }) + const res = await handleGetTokens(req) + expect(res.status).toBe(200) + const payload = await body(res) + expect(() => indexSchema.parse(payload.data)).not.toThrow() + }) + + it("conditional GET works on the per-token endpoint too", async () => { + const req = new Request("https://x/api/v1/tokens/ldo", { + headers: { "If-None-Match": currentEtag }, + }) + const res = await handleGetToken("ldo", req) + expect(res.status).toBe(304) + expect(await res.text()).toBe("") + }) +}) + +describe("GET /api/v1/openapi.json", () => { + it("is a valid-shaped OpenAPI 3.1 document with the four endpoints", async () => { + const res = await handleGetOpenApi() + expect(res.status).toBe(200) + expect(res.headers.get("Content-Type")).toBe("application/json") + expect(res.headers.get("ETag")).toBe(currentEtag) + + const doc = await body(res) + expect(doc.openapi).toBe("3.1.0") + expect(doc.info.version).toBe("v1") + for (const path of ["/tokens", "/tokens/{id}", "/framework", "/faq"]) { + expect(doc.paths[path]?.get, path).toBeTruthy() + } + }) + + it("emits component schemas generated from the Zod read-models", async () => { + const doc = await body(await handleGetOpenApi()) + for (const id of [ + "TokenIndex", + "TokenDoc", + "FrameworkDoc", + "Faq", + "Provenance", + "ApiError", + ]) { + expect(doc.components.schemas[id], id).toBeTruthy() + } + // Generated, not hand-duplicated: the dialect keys zod stamps are stripped. + expect(doc.components.schemas.Provenance.$schema).toBeUndefined() + // The provenance source enum is carried through from the Zod schema. + expect(doc.components.schemas.Provenance.properties.source.enum).toEqual([ + "generated", + "release", + ]) + }) + + it("every $ref resolves to a declared component schema", async () => { + const doc = await body(await handleGetOpenApi()) + const declared = new Set(Object.keys(doc.components.schemas)) + const refs: string[] = [] + const walk = (node: unknown) => { + if (Array.isArray(node)) { + for (const n of node) { + walk(n) + } + } else if (node && typeof node === "object") { + for (const [k, v] of Object.entries(node)) { + if (k === "$ref" && typeof v === "string") { + refs.push(v) + } else { + walk(v) + } + } + } + } + walk(doc) + expect(refs.length).toBeGreaterThan(0) + for (const r of refs) { + const name = r.replace("#/components/schemas/", "") + expect(declared.has(name), r).toBe(true) + } + }) + + it("returns 304 when If-None-Match matches", async () => { + const req = new Request("https://x/api/v1/openapi.json", { + headers: { "If-None-Match": currentEtag }, + }) + const res = await handleGetOpenApi(req) + expect(res.status).toBe(304) + expect(await res.text()).toBe("") + }) +}) + +describe("GET /api/v1 discovery root", () => { + it("points at the schema and guides and carries provenance", async () => { + const res = await handleGetDiscovery() + expect(res.status).toBe(200) + expect(res.headers.get("ETag")).toBe(currentEtag) + + const doc = await body(res) + expect(doc.version).toBe("v1") + expect(doc.schema).toBe("/api/v1/openapi.json") + expect(doc.guides).toEqual(["/llms.txt", "/agent-guide.md"]) + expect(doc.endpoints).toContain("/api/v1/tokens") + expect(() => provenanceSchema.parse(doc.provenance)).not.toThrow() + }) + + it("returns 304 when If-None-Match matches", async () => { + const req = new Request("https://x/api/v1/index.json", { + headers: { "If-None-Match": currentEtag }, + }) + const res = await handleGetDiscovery(req) + expect(res.status).toBe(304) + expect(await res.text()).toBe("") + }) +}) diff --git a/tests/generated-valid.test.ts b/tests/generated-valid.test.ts new file mode 100644 index 0000000..52da824 --- /dev/null +++ b/tests/generated-valid.test.ts @@ -0,0 +1,55 @@ +/** + * The committed read models (src/data/generated/ β€” produced by otf-cms's + * composer, synced here until the Blob/Edge Config backing lands) must parse + * against the vendored schema contract. + */ +import { readdirSync, readFileSync } from "node:fs" +import { join } from "node:path" +import { describe, expect, it } from "vitest" +import { + faqSchema, + frameworkDocSchema, + indexSchema, + manifestSchema, + testimonialsSchema, + tokenDocSchema, +} from "@/lib/schemas" + +const generated = join(__dirname, "..", "src", "data", "generated") +const readJson = (p: string) => JSON.parse(readFileSync(p, "utf8")) + +describe("src/data/generated/ parses against the vendored schema contract", () => { + it("index", () => { + expect(() => + indexSchema.parse(readJson(join(generated, "index.json"))) + ).not.toThrow() + }) + + it("token docs (one per manifest entry)", () => { + const manifest = manifestSchema.parse( + readJson(join(generated, "manifest.json")) + ) + const files = readdirSync(join(generated, "tokens")) + expect(files.map((f) => f.replace(/\.json$/, "")).sort()).toEqual( + [...manifest.tokens].sort() + ) + for (const id of manifest.tokens) { + expect( + () => tokenDocSchema.parse(readJson(join(generated, "tokens", `${id}.json`))), + id + ).not.toThrow() + } + }) + + it("framework, faq, testimonials", () => { + expect(() => + frameworkDocSchema.parse(readJson(join(generated, "framework.json"))) + ).not.toThrow() + expect(() => + faqSchema.parse(readJson(join(generated, "faq.json"))) + ).not.toThrow() + expect(() => + testimonialsSchema.parse(readJson(join(generated, "testimonials.json"))) + ).not.toThrow() + }) +}) diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..49ab7a6 --- /dev/null +++ b/vercel.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "headers": [ + { + "source": "/(.*)", + "headers": [ + { "key": "X-Content-Type-Options", "value": "nosniff" }, + { "key": "X-Frame-Options", "value": "SAMEORIGIN" }, + { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" } + ] + }, + { + "source": "/(llms.txt|agent-guide.md)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=300, s-maxage=86400, stale-while-revalidate=604800" + } + ] + } + ] +} diff --git a/vite.config.ts b/vite.config.ts index 6425263..5f912e3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, loadEnv } from 'vite' +import { defineConfig } from 'vite' import { devtools } from '@tanstack/devtools-vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import viteReact from '@vitejs/plugin-react' @@ -6,14 +6,8 @@ import viteTsConfigPaths from 'vite-tsconfig-paths' import tailwindcss from '@tailwindcss/vite' import { nitro } from 'nitro/vite' -const config = defineConfig(({ mode }) => { - const env = loadEnv(mode, process.cwd(), '') - const tokenSymbolEnv = env.VERCEL_GIT_COMMIT_REF ?? '' - +const config = defineConfig(() => { return { - define: { - 'import.meta.env.VITE_TOKEN_SYMBOL': JSON.stringify(tokenSymbolEnv), - }, plugins: [ devtools(), // Emit Vercel Build Output API artifacts instead of relying on Vercel diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4eacf64 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,14 @@ +import viteTsConfigPaths from "vite-tsconfig-paths" +import { defineConfig } from "vitest/config" + +export default defineConfig({ + plugins: [ + viteTsConfigPaths({ + projects: ["./tsconfig.json"], + }), + ], + test: { + environment: "node", + include: ["tests/**/*.test.ts"], + }, +})