Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .github/workflows/update-api-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: Update API Reference Docs

on:
schedule:
- cron: "0 6 * * 5"

workflow_dispatch:
inputs:
endpoint:
description: "WebSocket endpoint (blank = mainnet)"
required: false
default: ""

env:
NODE_VERSION: "20"
GENERATOR_DIR: "src/subtensor-docs-gen"
DOCS_DIR: "docs/subtensor-api"
BRANCH_PREFIX: "docs/auto-update"
SUBTENSOR_WS: >-
${{
inputs.endpoint != '' && inputs.endpoint
|| secrets.SUBTENSOR_WS_ENDPOINT != '' && secrets.SUBTENSOR_WS_ENDPOINT
|| 'wss://entrypoint-finney.opentensor.ai:443'
}}

jobs:
regenerate-docs:
name: Regenerate docs and open PR
runs-on: ubuntu-latest

steps:
- name: Generate GitHub App token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
id: app-token
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}

- name: Set up Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "yarn"
cache-dependency-path: ${{ env.GENERATOR_DIR }}/yarn.lock

- name: Install generator dependencies
working-directory: ${{ env.GENERATOR_DIR }}
run: yarn install --frozen-lockfile

- name: Generate API reference docs
working-directory: ${{ env.GENERATOR_DIR }}
env:
SUBTENSOR_WS: ${{ env.SUBTENSOR_WS }}
OUTPUT_DIR: "../../${{ env.DOCS_DIR }}"
run: yarn generate
timeout-minutes: 10

- name: Check for changes
id: diff
run: |
if git diff --quiet HEAD -- "$DOCS_DIR"; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No changes in $DOCS_DIR — nothing to PR."
else
# Check whether changes go beyond the generation-date stamp.
# Extract added/removed content lines, then strip out lines
# that only update the "Generated from a live snapshot … on **DATE**" header.
SUBSTANTIVE=$(git diff HEAD -- "$DOCS_DIR" \
| grep -E '^[+-]' \
| grep -vE '^(\+\+\+|---) ' \
| grep -vE 'Generated from a live snapshot of the Subtensor runtime on' \
| wc -l)

if [ "$SUBSTANTIVE" -eq 0 ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "Only generation-date changes detected — skipping PR."
git checkout -- "$DOCS_DIR"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
fi

- name: Commit updated docs to new branch
if: steps.diff.outputs.changed == 'true'
run: |
DATE=$(date -u +%Y-%m-%d)
BRANCH="${BRANCH_PREFIX}-${DATE}"
echo "BRANCH=${BRANCH}" >> "$GITHUB_ENV"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git checkout -b "$BRANCH"
git add "$DOCS_DIR"
git commit -m "docs: regenerate API reference $(date -u '+%Y-%m-%d %H:%M UTC')"
git push origin "$BRANCH"

- name: Build PR body
if: steps.diff.outputs.changed == 'true'
id: pr_body
run: |
STAT=$(git diff origin/main..HEAD -- "$DOCS_DIR" --stat | tail -6)
ADDED=$(git diff origin/main..HEAD -- "$DOCS_DIR" | grep -c '^+### ' || true)
REMOVED=$(git diff origin/main..HEAD -- "$DOCS_DIR" | grep -c '^-### ' || true)
CHANGED_FILES=$(git diff origin/main..HEAD -- "$DOCS_DIR" --name-only | sed 's/^/- /')

{
echo 'body<<PRBODYEOF'
echo "## 🤖 Automated API reference update"
echo ""
echo "Generated from the live Subtensor runtime on **$(date -u '+%Y-%m-%d %H:%M UTC')**."
echo ""
echo "**Endpoint:** \`${SUBTENSOR_WS}\`"
echo ""
echo "### Changed files"
echo "${CHANGED_FILES}"
echo ""
echo "### Diff summary"
echo "\`\`\`"
echo "${STAT}"
echo "\`\`\`"
echo ""
echo "| | Count |"
echo "|---|---|"
echo "| Headings added | ${ADDED} |"
echo "| Headings removed | ${REMOVED} |"
echo ""
echo "### Review checklist"
echo "- [ ] New \`###\` headings look correct (new extrinsics / storage items)"
echo "- [ ] Removed \`###\` headings are expected (renames / deprecations)"
echo "- [ ] Parameter types match the Rust source"
echo "- [ ] Summary text changes make sense"
echo ""
echo "---"
echo "_Opened automatically by the \`update-api-docs\` workflow. Merge once the diff looks correct._"
echo 'PRBODYEOF'
} >> "$GITHUB_OUTPUT"

- name: Open Pull Request
if: steps.diff.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
gh pr create \
--title "docs: API reference update $(date -u +%Y-%m-%d)" \
--head "$BRANCH" \
--base main \
--body "${{ steps.pr_body.outputs.body }}" \
--reviewer chideraao
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Dependencies
/node_modules
node_modules/

.obsidian
docs/.obsidian
Expand Down
2 changes: 1 addition & 1 deletion docs/evm-tutorials/bridge-vtao.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: "Token Bridging"

This guide provides an overview of two related topics:

- how to moves TAO between Substrate-style wallets (SS58) and the Etherum style wallets on Bittensor EVM
- how to move TAO between Substrate-style wallets (SS58) and the Etherum style wallets on Bittensor EVM
- how to use vTAO as a token bridge between Bittensor EVM and other EVM chains.

:::info
Expand Down
98 changes: 98 additions & 0 deletions src/subtensor-docs-gen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# subtensor-docs-gen

Generates the Markdown API reference at `docs/subtensor-api/` by connecting to a live
Subtensor node and introspecting the runtime metadata via `@polkadot/api`.

Modelled after the [polkadot.js docs generator](https://github.com/polkadot-js/docs).

## Prerequisites

- Node.js 18+
- Yarn 1.x (`npm install -g yarn` if not already installed)
- Access to a running Subtensor node (mainnet, testnet, or local)

## Setup

```bash
cd subtensor-docs-gen
yarn install
```

## Usage

```bash
yarn generate
```

### Custom endpoint or output path

```bash
SUBTENSOR_WS=wss://my-node.example.com:443 yarn generate

# Write to a different directory
OUTPUT_DIR=/tmp/subtensor-docs yarn generate
```

## Output

All files are written to `../docs/api-reference/`:

| File | Contents |
| --------------- | ------------------------------------------------------- |
| `extrinsics.md` | All `api.tx.*` dispatchable calls |
| `events.md` | All `api.events.*` event definitions |
| `errors.md` | All `api.errors.*` error variants |
| `storage.md` | All `api.query.*` storage items |
| `constants.md` | All `api.consts.*` runtime constants (with live values) |
| `rpc.md` | Custom Subtensor JSON-RPC methods |
| `runtime.md` | Custom Subtensor runtime calls methods |

## Project structure

```
subtensor-docs-gen/
├── package.json
├── tsconfig.json
├── src/
│ ├── generate.ts ← main entry point
│ ├── types.ts ← Bittensor custom SCALE types + RPC defs
│ ├── utils.ts ← shared Markdown helpers
│ └── generators/
│ ├── extrinsics.ts
│ ├── events.ts
│ ├── errors.ts
│ ├── storage.ts
│ ├── constants.ts
│ ├── rpc.ts
│ ├── runtime.ts
```

## How it works

1. `generate.ts` connects to the node via WebSocket using `@polkadot/api`.
2. The API performs a `state_getMetadata` RPC call, returning SCALE-encoded FRAME metadata.
3. Custom types from `types.ts` are registered so Bittensor-specific structs decode correctly.
4. Each generator module walks its namespace (`api.tx`, `api.events`, etc.), extracts doc comments and type info, and writes a Markdown file.
5. `constants.ts` additionally reads the _live values_ of each constant from the running node.
6. All files are written to the configured `OUTPUT_DIR`.

## Automation

A GitHub Actions workflow at `.github/workflows/update-api-docs.yml` runs every Friday at 06:00 UTC.
It opens a pull request with the updated docs when changes are detected.

You can also trigger it manually from the **Actions** tab with an optional custom endpoint.

## Adding new custom types

If Subtensor introduces a new pallet or struct, update `src/types.ts`:

```typescript
export const bittensorTypes = {
// existing types...
MyNewStruct: {
field_one: "u64",
field_two: "AccountId",
},
};
```
22 changes: 22 additions & 0 deletions src/subtensor-docs-gen/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "subtensor-docs-gen",
"version": "1.0.0",
"description": "Generates Markdown API reference docs from Subtensor runtime metadata via @polkadot/api",
"scripts": {
"generate": "ts-node src/generate.ts",
"generate:local": "SUBTENSOR_WS=ws://127.0.0.1:9944 ts-node src/generate.ts",
"generate:test": "SUBTENSOR_WS=wss://test.finney.opentensor.ai:443 ts-node src/generate.ts",
"lint": "tsc --noEmit"
},
"dependencies": {
"@polkadot/api": "^10.11.1",
"@polkadot/types": "^10.11.1",
"@polkadot/util": "^12.6.1"
},
"devDependencies": {
"@types/node": "^20.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
91 changes: 91 additions & 0 deletions src/subtensor-docs-gen/src/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* generate.ts
*
* Main entry point for the Subtensor API reference doc generator.
*
* Usage:
* yarn generate # mainnet (finney)
* yarn generate:local # local dev node
* yarn generate:test # testnet
* SUBTENSOR_WS=ws://... yarn ts-node src/generate.ts
*
* Output:
* Writes Markdown files to OUTPUT_DIR (default: ../docs/api-reference
* relative to the subtensor repo root, i.e. one level up from this project).
*/

import * as path from "path";
import { ApiPromise, WsProvider } from "@polkadot/api";
import { bittensorTypes, bittensorRpc } from "./types";
import { ensureDir } from "./utils";
import { generateExtrinsics } from "./generators/extrinsics";
import { generateEvents } from "./generators/events";
import { generateErrors } from "./generators/errors";
import { generateStorage } from "./generators/storage";
import { generateConstants } from "./generators/constants";
import { generateRpc } from "./generators/rpc";
import { generateRuntimeCalls } from "./generators/runtime";

// ── Configuration ─────────────────────────────────────────────────────────────

const WS_ENDPOINT =
process.env.SUBTENSOR_WS ?? "wss://entrypoint-finney.opentensor.ai:443";

const OUTPUT_DIR =
process.env.OUTPUT_DIR ??
path.resolve(__dirname, "..", "..", "..", "docs", "subtensor-api");

// ── Main ──────────────────────────────────────────────────────────────────────

async function main(): Promise<void> {
console.log("\n╔════════════════════════════════════════════════╗");
console.log("║ Subtensor API Reference — Doc Generator ║");
console.log("╚════════════════════════════════════════════════╝\n");
console.log(` Endpoint : ${WS_ENDPOINT}`);
console.log(` Output : ${OUTPUT_DIR}\n`);

// ── Connect ──────────────────────────────────────────────────────────────
console.log("▶ Connecting to node...");
const provider = new WsProvider(WS_ENDPOINT, 5_000 /* reconnect delay ms */);

const api = await ApiPromise.create({
provider,
types: bittensorTypes,
rpc: bittensorRpc,
});

await api.isReady;

const chain = (await api.rpc.system.chain()).toString();
const nodeName = (await api.rpc.system.name()).toString();
const nodeVer = (await api.rpc.system.version()).toString();
const specVer = api.runtimeVersion.specVersion.toString();

console.log(`\n Chain : ${chain}`);
console.log(` Node : ${nodeName} ${nodeVer}`);
console.log(` Spec ver : ${specVer}`);

// ── Prepare output directory ──────────────────────────────────────────────
ensureDir(OUTPUT_DIR);
console.log("\n▶ Generating docs...\n");

// ── Run all generators ────────────────────────────────────────────────────
generateExtrinsics(api, OUTPUT_DIR);
generateEvents(api, OUTPUT_DIR);
generateErrors(api, OUTPUT_DIR);
generateStorage(api, OUTPUT_DIR);
generateConstants(api, OUTPUT_DIR);
await generateRpc(api, OUTPUT_DIR);
generateRuntimeCalls(api, OUTPUT_DIR);

console.log("\n▶ Done.\n");

// ── Clean up ──────────────────────────────────────────────────────────────
await api.disconnect();
process.exit(0);
}

main().catch((err) => {
console.error("\n✖ Fatal error:", err);
process.exit(1);
});
Loading