diff --git a/docs/agents/auth-for-agents.mdx b/docs/agents/auth-for-agents.mdx index 131b552..4ba22f3 100644 --- a/docs/agents/auth-for-agents.mdx +++ b/docs/agents/auth-for-agents.mdx @@ -101,3 +101,4 @@ When auth is relevant, a useful agent answer usually contains: - [Auth & Access](/auth) - [Agents on FastNear](/agents) - [Choosing the Right Surface](/agents/choosing-surfaces) +- [Building an MCP Server with FastNear](/agents/mcp) diff --git a/docs/agents/choosing-surfaces.mdx b/docs/agents/choosing-surfaces.mdx index e446dc6..feeba5e 100644 --- a/docs/agents/choosing-surfaces.mdx +++ b/docs/agents/choosing-surfaces.mdx @@ -16,6 +16,8 @@ Do not start by exposing every FastNear API to an agent. Start by translating th For agents, the important question is usually not "which endpoint exists?" It is "what kind of answer will help the user next?" +If your task is designing an MCP tool set rather than routing one user request, use [Building an MCP Server with FastNear](/agents/mcp). + ## What decides the route Before you pick an API, identify four things: @@ -263,3 +265,4 @@ The goal is not to prove that multiple FastNear APIs exist. The goal is to answe - [Agents on FastNear](/agents) for the full surface map, base URLs, and prompt-ingestion hints. - [Auth for Agents](/agents/auth) for credential handling and runtime posture. - [Agent Playbooks](/agents/playbooks) for example multi-step workflows. +- [Building an MCP Server with FastNear](/agents/mcp) for a recommended first tool set and a direct-HTTP TypeScript example. diff --git a/docs/agents/index.mdx b/docs/agents/index.mdx index 3ae2cf4..2e352f3 100644 --- a/docs/agents/index.mdx +++ b/docs/agents/index.mdx @@ -19,6 +19,7 @@ This page is the operational starting point for AI agents, crawlers, and automat - Need to decide which FastNear API to start with? Use [Choosing the Right Surface](/agents/choosing-surfaces). - Need credential handling rules? Use [Auth for Agents](/agents/auth). - Need example multi-step workflows? Use [Agent Playbooks](/agents/playbooks). +- Need to expose FastNear to Claude Desktop, Codex, Cursor, or another MCP client? Use [Building an MCP Server with FastNear](/agents/mcp). - Need exact endpoint docs now? Go directly to [RPC Reference](/rpc), [FastNear API](/api), [Transactions API](/tx), [Transfers API](/transfers), [NEAR Data API](/neardata), or [KV FastData API](/fastdata/kv). ## FastNear for agents in one minute @@ -145,3 +146,4 @@ Avoid dumping raw payloads when the user is really asking for interpretation. - Need routing depth and tradeoffs? [Choosing the Right Surface](/agents/choosing-surfaces) - Need credential posture and secret handling? [Auth for Agents](/agents/auth) - Need example workflows? [Agent Playbooks](/agents/playbooks) +- Need to build your own MCP tool surface on top of FastNear? [Building an MCP Server with FastNear](/agents/mcp) diff --git a/docs/agents/mcp.mdx b/docs/agents/mcp.mdx new file mode 100644 index 0000000..c7dffa7 --- /dev/null +++ b/docs/agents/mcp.mdx @@ -0,0 +1,484 @@ +--- +title: Building an MCP Server with FastNear +description: Use FastNear RPC and REST APIs inside your own MCP server with a direct-HTTP TypeScript example. +slug: /agents/mcp +sidebar_position: 5 +displayed_sidebar: null +page_actions: + - markdown +--- + +# Building an MCP Server with FastNear + +{/* FASTNEAR_AI_DISCOVERY: This page is for developers who want to expose FastNear through their own MCP server. It recommends an initial tool set, explains why direct HTTP is a good default, and includes a copy-paste TypeScript example. */} + +This page is for teams that want to expose FastNear through their own MCP server for Claude Desktop, Codex, Cursor, or another MCP client. `docs.fastnear.com` is not itself an MCP server. The goal here is to show a clean starting point for building one on top of FastNear's existing RPC and REST APIs. + +## When MCP is worth it + +Build an MCP server when you want one stable tool surface in front of the FastNear APIs: + +- your team uses a desktop or IDE agent and wants chain data available as tools +- you want the agent to choose between account, transaction, block, and RPC surfaces on its own +- you want to hide auth handling and base URLs behind a small trusted backend or local process +- you want a reusable tool contract instead of asking every agent or workflow to call raw HTTP directly + +If your workflow already controls HTTP directly and does not need tool discovery, MCP may be unnecessary. FastNear's docs and APIs already work well as plain HTTP surfaces. + +## Recommended first tool set + +Start with a few broad tools instead of mirroring every endpoint one-for-one: + +| MCP tool | FastNear surface | Use it for | +| --- | --- | --- | +| `get-account-summary` | [V1 Full Account View](/api/v1/account-full) | balances, holdings, staking, wallet-style summaries | +| `lookup-public-key` | [V1 Public Key Lookup](/api/v1/public-key) | resolving a public key to one or more accounts | +| `get-transactions-by-hash` | [Transactions by Hash](/tx/transactions) | transaction investigation and readable execution follow-up | +| `get-latest-final-block` | [Last Final Block Redirect](/neardata/last-block-final) | latest finalized head checks and polling workflows | +| `view-account-rpc` | [View Account](/rpc/account/view-account) | exact canonical account state when indexed summaries are not enough | + +That tool set covers most "what does this account have?", "what happened to this transaction?", and "what is the latest finalized block?" workflows without forcing the client to understand the full FastNear surface area up front. + +## Why this example uses direct HTTP + +This example intentionally uses raw `fetch()` calls instead of `near-api-js`. + +- The docs you are reading are organized around the raw RPC and REST contracts. +- Direct HTTP keeps the MCP tool behavior aligned with the docs one-for-one. +- You avoid SDK abstraction gaps, version drift, and helper behavior that can hide the actual wire format. +- For read-heavy MCP tools, direct HTTP is usually the simplest thing that works. + +If you later need transaction signing, account helpers, or wallet-specific flows in the same process, `near-api-js` can still make sense. For a teaching example focused on FastNear's RPC and APIs, direct calls are the cleaner default. + +## Install + +Use Node.js 20 or newer so `fetch()` is available globally. + +```bash +mkdir fastnear-mcp +cd fastnear-mcp +npm init -y +npm install @modelcontextprotocol/sdk zod +npm install -D tsx typescript @types/node +``` + +The official MCP TypeScript SDK docs currently recommend the stable `v1.x` line for production use. The install command above will resolve the current stable package release. + +## Copy-paste TypeScript example + +Create `fastnear-mcp.ts`: + +```ts title="fastnear-mcp.ts" +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; + +type Network = "mainnet" | "testnet"; + +const DEFAULT_NETWORK: Network = + process.env.FASTNEAR_DEFAULT_NETWORK === "testnet" ? "testnet" : "mainnet"; + +const URLS: Record< + Network, + { + api: string; + rpc: string; + neardata: string; + tx: string; + } +> = { + mainnet: { + api: "https://api.fastnear.com", + rpc: "https://rpc.mainnet.fastnear.com", + neardata: "https://mainnet.neardata.xyz", + tx: "https://tx.main.fastnear.com", + }, + testnet: { + api: "https://test.api.fastnear.com", + rpc: "https://rpc.testnet.fastnear.com", + neardata: "https://testnet.neardata.xyz", + tx: "https://tx.test.fastnear.com", + }, +}; + +const apiKey = process.env.FASTNEAR_API_KEY; + +function selectNetwork(network?: Network): Network { + return network ?? DEFAULT_NETWORK; +} + +function authHeaders(extra: HeadersInit = {}): HeadersInit { + if (!apiKey) { + return extra; + } + + return { + ...extra, + Authorization: `Bearer ${apiKey}`, + }; +} + +async function requestJson(url: string, init?: RequestInit): Promise { + const response = await fetch(url, init); + const text = await response.text(); + + let body: unknown = null; + + if (text) { + try { + body = JSON.parse(text); + } catch { + body = text; + } + } + + if (!response.ok) { + const detail = + typeof body === "string" ? body : JSON.stringify(body, null, 2); + throw new Error(`${response.status} ${response.statusText}: ${detail}`); + } + + return body; +} + +function toolResult(data: unknown) { + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(data, null, 2), + }, + ], + }; +} + +function toolError(error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return { + isError: true, + content: [ + { + type: "text" as const, + text: message, + }, + ], + }; +} + +async function runTool(work: () => Promise) { + try { + return toolResult(await work()); + } catch (error) { + return toolError(error); + } +} + +function toIsoFromNanoseconds(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + + try { + return new Date(Number(BigInt(value) / 1_000_000n)).toISOString(); + } catch { + return null; + } +} + +async function getAccountSummary(network: Network, accountId: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/account/${encodeURIComponent(accountId)}/full`, + { + headers: authHeaders(), + }, + ); +} + +async function lookupPublicKey(network: Network, publicKey: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/public_key/${encodeURIComponent(publicKey)}`, + { + headers: authHeaders(), + }, + ); +} + +async function getTransactionsByHash(network: Network, txHashes: string[]) { + const baseUrl = URLS[network].tx; + return requestJson(`${baseUrl}/v0/transactions`, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + tx_hashes: txHashes, + }), + }); +} + +async function getLatestFinalBlock(network: Network) { + const baseUrl = URLS[network].neardata; + const result = (await requestJson(`${baseUrl}/v0/last_block/final`, { + headers: authHeaders(), + })) as { + block?: { + author?: string; + chunks?: unknown[]; + header?: { + height?: number; + hash?: string; + prev_hash?: string; + timestamp_nanosec?: string; + }; + }; + }; + + const block = result.block; + const header = block?.header; + + return { + network, + source: "NEAR Data API", + finality: "final", + block_height: header?.height ?? null, + block_hash: header?.hash ?? null, + prev_block_hash: header?.prev_hash ?? null, + author: block?.author ?? null, + timestamp_nanosec: header?.timestamp_nanosec ?? null, + timestamp_iso: toIsoFromNanoseconds(header?.timestamp_nanosec), + chunk_count: Array.isArray(block?.chunks) ? block.chunks.length : null, + }; +} + +async function viewAccountRpc( + network: Network, + accountId: string, + finality: "final" | "optimistic", +) { + const rpcUrl = URLS[network].rpc; + + return requestJson(rpcUrl, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + jsonrpc: "2.0", + id: `view-account:${accountId}`, + method: "query", + params: { + request_type: "view_account", + finality, + account_id: accountId, + }, + }), + }); +} + +const server = new McpServer({ + name: "fastnear-direct-http", + version: "0.1.0", +}); + +server.registerTool( + "get-account-summary", + { + title: "Get account summary", + description: + "Fetch a combined FastNear account view with balances, assets, and staking data.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, network }) => + runTool(() => getAccountSummary(selectNetwork(network), accountId)), +); + +server.registerTool( + "lookup-public-key", + { + title: "Lookup public key", + description: + "Resolve a public key to one or more NEAR accounts using the FastNear API.", + inputSchema: { + publicKey: z + .string() + .min(16) + .describe("Public key, for example ed25519:..."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ publicKey, network }) => + runTool(() => lookupPublicKey(selectNetwork(network), publicKey)), +); + +server.registerTool( + "get-transactions-by-hash", + { + title: "Get transactions by hash", + description: + "Fetch up to 20 transactions by hash from the Transactions API.", + inputSchema: { + txHashes: z + .array(z.string().min(32)) + .min(1) + .max(20) + .describe("One or more base58 transaction hashes."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ txHashes, network }) => + runTool(() => getTransactionsByHash(selectNetwork(network), txHashes)), +); + +server.registerTool( + "get-latest-final-block", + { + title: "Get latest final block", + description: + "Fetch a compact summary of the latest finalized block from the NEAR Data API.", + inputSchema: { + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ network }) => + runTool(() => getLatestFinalBlock(selectNetwork(network))), +); + +server.registerTool( + "view-account-rpc", + { + title: "View account via RPC", + description: + "Fetch canonical account state directly from NEAR JSON-RPC.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + finality: z + .enum(["final", "optimistic"]) + .optional() + .describe("Defaults to final."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, finality, network }) => + runTool(() => + viewAccountRpc( + selectNetwork(network), + accountId, + finality ?? "final", + ), + ), +); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); +``` + +Start it locally: + +```bash +FASTNEAR_API_KEY=your_key_here \ +FASTNEAR_DEFAULT_NETWORK=mainnet \ +npx tsx fastnear-mcp.ts +``` + +`FASTNEAR_API_KEY` is optional for many public reads, but it is the right default for authenticated runtimes and higher-limit traffic. + +The raw `NEAR Data API` helper at `/v0/last_block/final` redirects to the current block route. Standard `fetch()` follows that redirect automatically, which is why the example does not need extra redirect-handling code. + +## Generic client configuration + +Keep this page generic. Most local MCP clients use nearly the same ingredients even when their file location or JSON shape differs a bit: `command`, `args`, `env`, and sometimes `cwd`. + +Many local MCP clients accept something close to this: + +```json title="Example MCP client config" +{ + "mcpServers": { + "fastnear": { + "command": "npx", + "args": ["tsx", "/absolute/path/to/fastnear-mcp.ts"], + "env": { + "FASTNEAR_API_KEY": "your_key_here", + "FASTNEAR_DEFAULT_NETWORK": "mainnet" + } + } + } +} +``` + +If your MCP client launches commands from some other working directory, either set its `cwd` or `workingDirectory` option when available, or replace `npx tsx` with an absolute path to the local `tsx` binary. The important part is that the client can resolve the locally installed `tsx` package before it starts `fastnear-mcp.ts`. + +One generic config example is usually enough for a page like this. Product-specific snippets and "open in ..." affordances tend to change faster than the MCP tool contract itself. + +For remote or team-wide deployment, start with this same tool surface and then switch from `stdio` to a network transport only when you actually need a hosted server. + +## Tool design checklist + +When you turn FastNear into MCP tools, these defaults usually hold up well: + +- Name tools after user jobs, not endpoint paths. +- Start with three to six tools, not fifty. +- Use [FastNear API](/api) for summaries and resolution tasks, [Transactions API](/tx) for readable history, and [RPC Reference](/rpc) only when canonical protocol semantics matter. +- Keep `network` optional but explicit, with a sensible default. +- Return concise JSON. Avoid huge payloads when the tool only needs one slice of the response. +- Polling-oriented tools like latest-block helpers should return summaries by default, not full block bodies. +- Keep API keys in env vars or a secret manager and attach them server-side. +- Preserve opaque pagination tokens exactly as FastNear returned them. +- Tell the caller when a tool returns indexed summary data versus canonical RPC data. +- Keep one tool responsible for one kind of answer. Do not create several overlapping tools that all partly answer the same question. + +## Common pitfalls + +- Do not expose every FastNear endpoint as its own MCP tool on day one. +- Do not force account-summary workflows through raw RPC if the indexed API already answers the user's real question. +- Do not hide the difference between indexed data and canonical node data. +- Do not put `FASTNEAR_API_KEY` into prompts, browser storage, or checked-in config. +- Do not turn `NEAR Data API` into a fake streaming abstraction. It is a polling-oriented read surface. + +## Good next additions + +Once the basic server is working, the next useful tools are usually: + +- account history on top of [Transactions API](/tx/account) +- transfer-only history on top of [Transfers API](/transfers/query) +- contract view calls on top of [Call a Function](/rpc/contract/call-function) +- a small resource or prompt that explains your own team's default routing and auth rules + +## Related guides + +- [Agents on FastNear](/agents) +- [Choosing the Right Surface](/agents/choosing-surfaces) +- [Auth for Agents](/agents/auth) +- [Agent Playbooks](/agents/playbooks) diff --git a/docs/agents/playbooks.mdx b/docs/agents/playbooks.mdx index fdca197..5d91723 100644 --- a/docs/agents/playbooks.mdx +++ b/docs/agents/playbooks.mdx @@ -267,4 +267,5 @@ If the request is still ambiguous after reading this page: - use [Choosing the Right Surface](/agents/choosing-surfaces) to pick the first API - use [Auth for Agents](/agents/auth) if the blocker is credential handling +- use [Building an MCP Server with FastNear](/agents/mcp) if you are designing a reusable MCP tool surface rather than answering one user task - return to [Agents on FastNear](/agents) for the default workflow and answer-shape rules diff --git a/docs/rpc/transaction/broadcast-tx-commit.mdx b/docs/rpc/transaction/broadcast-tx-commit.mdx index 9302612..3fb18d7 100644 --- a/docs/rpc/transaction/broadcast-tx-commit.mdx +++ b/docs/rpc/transaction/broadcast-tx-commit.mdx @@ -12,5 +12,6 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `broadcast_tx_commit` request type +Use a freshly signed base64 transaction for each request. This legacy synchronous send is still documented, but `send_tx` is the preferred path for new integrations, and the sample payload is illustrative only. diff --git a/docs/rpc/transaction/send-tx.mdx b/docs/rpc/transaction/send-tx.mdx index dd66911..aacf060 100644 --- a/docs/rpc/transaction/send-tx.mdx +++ b/docs/rpc/transaction/send-tx.mdx @@ -12,5 +12,6 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `send_tx` request type +Use a freshly signed base64 transaction for each request. The sample payload shown in the interactive docs is illustrative only and will fail if you paste it unchanged. diff --git a/docs/snapshots/index.mdx b/docs/snapshots/index.mdx index d9d1bb8..9238c23 100644 --- a/docs/snapshots/index.mdx +++ b/docs/snapshots/index.mdx @@ -2,7 +2,7 @@ sidebar_position: 2 slug: /snapshots title: Validator snapshots -description: Snapshot download paths for FastNear-backed NEAR node bootstrap and recovery workflows. +description: Current FastNear snapshot status and operator guidance for NEAR node bootstrap and archival snapshot requests. sidebar_label: Snapshots Overview displayed_sidebar: snapshotsSidebars keywords: @@ -21,49 +21,51 @@ import SimpleButton from '@site/src/components/SimpleButton'; This section is for node operators who are bootstrapping or recovering NEAR infrastructure. It is not an application-data surface. If the job is reading balances, history, blocks, or contract state, use the API and RPC docs instead of snapshot workflows. :::warning[Free snapshots are deprecated] -The free nearcore data snapshots have been deprecated. +The free nearcore data snapshots were deprecated on June 1, 2025. The NEAR Infrastructure Committee and Near One recommend Epoch Sync plus decentralized state sync. Use NEAR Nodes for the current operator guidance and recommended bootstrap posture. ::: -## Use this section when +## Current status -- you need to bootstrap a mainnet or testnet node from snapshot data -- you are recovering an RPC or archival node -- you already know you want the FastNear snapshot download path +- FastNear does not currently publish a working public self-serve snapshot download flow from this docs site. +- For regular RPC nodes and validators, follow NEAR Nodes and use the current Epoch Sync plus decentralized state sync guidance. +- For archival snapshot access, visit fastnear.com/snapshots or email snapshots@fastnear.com. +- The legacy scripts in fastnear/static still exist, but the old public `latest.txt` discovery flow is no longer the supported public path here. -## Do not use this section when +## Useful operator references -- you are trying to query chain data for an application -- you need recent blocks, balances, history, or contract state -- you are looking for general product API guidance rather than operator setup +- [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync) for the current boot-node refresh step before first start. +- [NEAR Nodes: State Sync](https://near-nodes.io/rpc/state-sync) for the current non-snapshot bootstrap recommendation. +- [NEAR Nodes: Run an Archival Node](https://near-nodes.io/archival/run-archival-node) for archival node configuration and data-directory expectations. +- [NEAR Nodes: Split Storage for NEAR Archival Nodes](https://near-nodes.io/archival/split-storage-archival) if you are planning hot/cold storage for modern archival setups. -In those cases, use [RPC Reference](/rpc), [FastNear API](/api), [Transactions API](/tx), or [NEAR Data API](/neardata). +## What to have ready before you request archival access -## Before you download +- the network: `mainnet` or `testnet` +- the node role: validator, RPC node, or archival node +- whether you expect a single `~/.near/data` restore or a split hot/cold layout +- the target `NEAR_HOME` path and the storage layout you plan to use +- any time sensitivity, bandwidth constraints, or recovery deadline that affects the transfer plan -- Choose the network first: mainnet or testnet. -- Decide whether you need regular RPC data or archival data. -- Make sure you understand where hot and cold data must live before starting an archival download. -- Install `rclone`, because the download scripts depend on it. +## Use this section when -:::info[Getting `rclone`] -Install `rclone` with: +- you are confirming the current FastNear snapshot posture for mainnet or testnet +- you need the right request path for archival snapshot access +- you are deciding whether to use snapshots or the standard NEAR node bootstrap guidance -```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash -``` -::: - -## What each path covers +## Do not use this section when -- **Mainnet** includes optimized `fast-rpc`, standard RPC, and archival hot/cold download paths. -- **Testnet** includes RPC and archival snapshot paths for testnet operators. +- you are trying to query chain data for an application +- you need recent blocks, balances, history, or contract state +- you are looking for general product API guidance rather than operator setup -See nearcore for node requirements, and fastnear/static for the snapshot download script source used by these guides. +In those cases, use [RPC Reference](/rpc), [FastNear API](/api), [Transactions API](/tx), or [NEAR Data API](/neardata). ## Choose a network +Use the network pages below for the current status and escalation path. They no longer assume a public self-serve download URL. +
Mainnet Snapshots Testnet Snapshots diff --git a/docs/snapshots/mainnet.mdx b/docs/snapshots/mainnet.mdx index 210230b..dc15144 100644 --- a/docs/snapshots/mainnet.mdx +++ b/docs/snapshots/mainnet.mdx @@ -1,134 +1,46 @@ --- sidebar_label: Mainnet slug: /snapshots/mainnet -description: Download mainnet RPC and archival snapshots for FastNear-backed NEAR infrastructure bootstrapping. +description: Current FastNear mainnet snapshot posture for RPC bootstrap and archival snapshot requests. --- # Mainnet -## Optimized Mainnet Snapshot +FastNear no longer documents a public self-serve mainnet snapshot download flow here. -This is likely the preferred approach for syncing, as opposed to downloading an archival snapshot, which is significantly larger and more special-purpose. - -Nodes with sufficient resources can take advantage of setting the `$RPC_TYPE` flag to `fast-rpc`. (Default is `rpc`) - -Before running the snapshot download script, you can set the following environment variables: - -- `CHAIN_ID` to either `mainnet` or `testnet`. (default: `mainnet`) -- `RPC_TYPE` to either `rpc` (default) or `fast-rpc` -- `THREADS` to the number of threads you want to use for downloading. Use `128` for 10Gbps, and `16` for 1Gbps (default: `128`). -- `TPSLIMIT` to the maximum number of HTTP new actions per second. (default: `4096`) -- `BWLIMIT` to the maximum bandwidth to use for download in case you want to limit it. (default: `10G`) -- `DATA_PATH` to the path where you want to download the snapshot (default: `~/.near/data`) -- `BLOCK` to the block height of the snapshot you want to download. If not set, it will download the latest snapshot. - -**Run this command to download the RPC Mainnet snapshot:** - -:::info -We will set the following environment variables: -- `DATA_PATH=~/.near/data` - the standard nearcore path -- `CHAIN_ID=mainnet` - to explicitly specify the mainnet data -- `RPC_TYPE=fast-rpc` - select optimized approach -::: - -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## RPC Mainnet Snapshot - -This is the standard method to obtain a snapshot without the high performance from the previous section covering optimized snapshots. - -Before running the snapshot download script, you can set the following environment variables: - -- `CHAIN_ID` to either `mainnet` or `testnet`. (default: `mainnet`) -- `RPC_TYPE` to either `rpc` (default) or `fast-rpc` -- `THREADS` to the number of threads you want to use for downloading. Use `128` for 10Gbps, and `16` for 1Gbps (default: `128`). -- `TPSLIMIT` to the maximum number of HTTP new actions per second. (default: `4096`) -- `BWLIMIT` to the maximum bandwidth to use for download in case you want to limit it. (default: `10G`) -- `DATA_PATH` to the path where you want to download the snapshot (default: `~/.near/data`) -- `BLOCK` to the block height of the snapshot you want to download. If not set, it will download the latest snapshot. - -**Run this command to download the RPC Mainnet snapshot:** - -:::info -We will set the following environment variables: -- `DATA_PATH=~/.near/data` - the standard nearcore path -- `CHAIN_ID=mainnet` - to explicitly specify the mainnet data +:::warning[Free snapshots are deprecated] +The free nearcore data snapshots were deprecated on June 1, 2025. ::: -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet bash -``` - -## Archival Mainnet snapshot - -:::warning -**Time and storage intensive.** - -Be prepared for a large download and the inherent time constraints involved. - -The snapshot size is ~60Tb and contains more than 1M files. -::: +## For regular RPC nodes and validators -Before running the download script, you can set the following environment variables: +Use [NEAR Nodes](https://near-nodes.io) for the current bootstrap guidance based on Epoch Sync plus decentralized state sync. That is the public recommendation for normal mainnet node bring-up and recovery. -- `CHAIN_ID` to either `mainnet` or `testnet`. (default: `mainnet`) -- `THREADS` to the number of threads you want to use for downloading. Use `128` for 10Gbps, and `16` for 1Gbps (default: `128`). -- `TPSLIMIT` to the maximum number of HTTP new actions per second. (default: `4096`) -- `DATA_TYPE` to either `hot-data` or `cold-data` (default: `cold-data`) -- `BWLIMIT` to the maximum bandwidth to use for download in case you want to limit it. (default: `10G`) -- `DATA_PATH` to the path where you want to download the snapshot (default: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` to the block height of the snapshot you want to download. If not set, it will download the latest snapshot. +Before the first `neard run`, refresh `network.boot_nodes` with the current command from [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). A freshly downloaded `config.json` can still leave `boot_nodes` empty, which can leave the node stuck at `Waiting for peers 0 peers`. -By default, the script assumes the paths for the data: -- Hot data (has to be on NVME): `/mnt/nvme/data/hot-data` -- Cold data (can be on HDDs): `/mnt/nvme/data/cold-data` +## For archival mainnet snapshots +Archival snapshot access is request-based. Start from [fastnear.com/snapshots](https://fastnear.com/snapshots) or email [snapshots@fastnear.com](mailto:snapshots@fastnear.com) to get the current availability, block height, and storage expectations. -**Run the following commands to download the Archival Mainnet snapshot:** +## About the legacy scripts -1. Download the latest snapshot block height: +The [fastnear/static](https://github.com/fastnear/static) repository still contains the historical `down_rclone.sh` and `down_rclone_archival.sh` scripts. These docs no longer publish copy-paste commands for them because the old public `latest.txt` discovery URLs now redirect to the snapshots info page and are not a supported public workflow. -`Latest archival mainnet snapshot block`: +If FastNear support gives you a current block height and a supported download path, use the exact host, block, and storage layout they provide instead of the retired public examples. -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +## If you are preparing for an archival request -2. Download the HOT data from the snapshot. It has to be placed on NVME. +- Be ready to separate hot and cold data if the snapshot package requires it. +- Confirm your storage layout with nearcore before you start the transfer. +- Install `rclone` only if the support flow you receive actually depends on it. -:::info -We will set the following environment variables: -- `DATA_TYPE=hot-data` - downloads the Hot data -- `DATA_PATH=~/.near/data` - the standard nearcore path -- `CHAIN_ID=mainnet` - to explicitly specify the mainnet data -- `BLOCK=$LATEST` - specify the snapshot block -::: - -`Archival Mainnet Snapshot (hot-data) » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` - -3. Download the COLD data from the snapshot. It can be placed on HDDs. - -:::info -We will set the following environment variables: -- `DATA_TYPE=cold-data` - downloads the Hot data -- `DATA_PATH=/mnt/hdds/cold-data` - the path where to place cold data. **Note**: the nearcore config should point cold data store to the same path. -- `CHAIN_ID=mainnet` - to explicitly specify the mainnet data -- `BLOCK=$LATEST` - specify the snapshot block -::: +## If FastNear provides a snapshot package -`Archival Mainnet Snapshot (cold-data) » /mnt/hdds/cold-data`: +1. Stop `neard` before you replace any snapshot data or edit archival storage settings. +2. Confirm whether the package is meant for `~/.near/data` or for split `hot-data` and `cold-data` paths. +3. Verify that your `config.json` matches the intended archival mode before restart. + For archival nodes, NEAR Nodes documents `archive: true` and `tracked_shards: [0]` as critical settings. +4. If the package uses split storage, make sure your configured store paths match the delivered hot/cold layout before you start the node again. +5. Restart the node and verify that it starts cleanly and resumes syncing instead of silently serving stale local state. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=cold-data DATA_PATH=/mnt/hdds/cold-data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` +For nearcore storage expectations and operator prerequisites, see [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/docs/snapshots/testnet.mdx b/docs/snapshots/testnet.mdx index 434c3d6..6a400e2 100644 --- a/docs/snapshots/testnet.mdx +++ b/docs/snapshots/testnet.mdx @@ -1,82 +1,46 @@ --- sidebar_label: Testnet slug: /snapshots/testnet -description: Download testnet RPC and archival snapshots for FastNear-backed NEAR infrastructure bootstrapping. +description: Current FastNear testnet snapshot posture for RPC bootstrap and archival snapshot requests. --- # Testnet -## RPC Testnet Snapshot +FastNear no longer documents a public self-serve testnet snapshot download flow here. -This is likely the preferred approach for syncing, as opposed to downloading an archival snapshot, which is significantly larger and more special-purpose. - -Before running the snapshot download script, you can set the following environment variables: - -- `CHAIN_ID` to either `mainnet` or `testnet`. (default: `mainnet`) -- `THREADS` to the number of threads you want to use for downloading. Use `128` for 10Gbps, and `16` for 1Gbps (default: `128`). -- `TPSLIMIT` to the maximum number of HTTP new actions per second. (default: `4096`) -- `BWLIMIT` to the maximum bandwidth to use for download in case you want to limit it. (default: `10G`) -- `DATA_PATH` to the path where you want to download the snapshot (default: `~/.near/data`) -- `BLOCK` to the block height of the snapshot you want to download. If not set, it will download the latest snapshot. - -**Run this command to download the RPC Testnet snapshot:** - -:::info -We will set the following environment variables: -- `DATA_PATH=~/.near/data` - the standard nearcore path -- `CHAIN_ID=testnet` - to explicitly specify the testnet data +:::warning[Free snapshots are deprecated] +The free nearcore data snapshots were deprecated on June 1, 2025. ::: -`RPC Testnet Snapshot » ~/.near/data`: +## For regular RPC nodes and validators -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=testnet bash -``` +Use [NEAR Nodes](https://near-nodes.io) for the current bootstrap guidance based on Epoch Sync plus decentralized state sync. That is the public recommendation for normal testnet node bring-up and recovery. -## Archival Testnet snapshot +Before the first `neard run`, refresh `network.boot_nodes` with the current command from [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). A freshly downloaded `config.json` can still leave `boot_nodes` empty, which can leave the node stuck at `Waiting for peers 0 peers`. -:::warning -**Time and storage intensive.** +## For archival testnet snapshots -Be prepared for a large download and the inherent time constraints involved. -::: +Archival snapshot access is request-based. Start from [fastnear.com/snapshots](https://fastnear.com/snapshots) or email [snapshots@fastnear.com](mailto:snapshots@fastnear.com) to get the current availability, block height, and storage expectations. -Before running the download script, you can set the following environment variables: +## About the legacy scripts -- `CHAIN_ID` to either `mainnet` or `testnet`. (default: `mainnet`) -- `THREADS` to the number of threads you want to use for downloading. Use `128` for 10Gbps, and `16` for 1Gbps (default: `128`). -- `TPSLIMIT` to the maximum number of HTTP new actions per second. (default: `4096`) -- `DATA_TYPE` to either `hot-data` or `cold-data` (default: `cold-data`) -- `BWLIMIT` to the maximum bandwidth to use for download in case you want to limit it. (default: `10G`) -- `DATA_PATH` to the path where you want to download the snapshot (default: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` to the block height of the snapshot you want to download. If not set, it will download the latest snapshot. +The [fastnear/static](https://github.com/fastnear/static) repository still contains the historical `down_rclone.sh` and `down_rclone_archival.sh` scripts. These docs no longer publish copy-paste commands for them because the old public `latest.txt` discovery URLs now redirect to the snapshots info page and are not a supported public workflow. -By default the script assumes the paths for the data: -- Hot data (has to be on NVME): `/mnt/nvme/data/hot-data` +If FastNear support gives you a current block height and a supported download path, use the exact host, block, and storage layout they provide instead of the retired public examples. -**Run the following commands to download the Archival Testnet snapshot:** +## If you are preparing for an archival request -1. Download the latest snapshot block height: +- Be ready to place hot data on fast local storage if the snapshot package requires it. +- Confirm your storage layout with nearcore before you start the transfer. +- Install `rclone` only if the support flow you receive actually depends on it. -`Latest archival testnet snapshot block`: - -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` - -2. Download the HOT data from the snapshot. It has to be placed on NVME. - -:::info -We will set the following environment variables: -- `DATA_TYPE=hot-data` - downloads the Hot data -- `DATA_PATH=~/.near/data` - the standard nearcore path -- `CHAIN_ID=testnet` - set to testnet network -- `BLOCK=$LATEST` - specify the snapshot block -::: +## If FastNear provides a snapshot package -`Archival Testnet Snapshot (hot-data) » ~/.near/data`: +1. Stop `neard` before you replace any snapshot data or edit archival storage settings. +2. Confirm whether the package is meant for `~/.near/data` or for split `hot-data` and `cold-data` paths. +3. Verify that your `config.json` matches the intended archival mode before restart. + For archival nodes, NEAR Nodes documents `archive: true` and `tracked_shards: [0]` as critical settings. +4. If the package uses split storage, make sure your configured store paths match the delivered hot/cold layout before you start the node again. +5. Restart the node and verify that it starts cleanly and resumes syncing instead of silently serving stale local state. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=testnet BLOCK=$LATEST bash -``` +For nearcore storage expectations and operator prerequisites, see [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/agents/auth-for-agents.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/agents/auth-for-agents.mdx index 035ae2c..0528530 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/auth-for-agents.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/auth-for-agents.mdx @@ -101,3 +101,4 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - [Аутентификация и доступ](/auth) - [Агенты на FastNear](/agents) - [Как выбрать подходящую поверхность](/agents/choosing-surfaces) +- [Как построить MCP-сервер на FastNear](/agents/mcp) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/agents/choosing-surfaces.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/agents/choosing-surfaces.mdx index ae70780..643896f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/choosing-surfaces.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/choosing-surfaces.mdx @@ -16,6 +16,8 @@ page_actions: Для агента важнее не вопрос «какой эндпоинт существует?», а вопрос «какой ответ поможет пользователю сделать следующий шаг?». +Если ваша задача — спроектировать набор инструментов для MCP-сервера, а не маршрутизировать один пользовательский запрос, используйте [Как построить MCP-сервер на FastNear](/agents/mcp). + ## Что определяет маршрут Прежде чем выбрать API, определите четыре вещи: @@ -263,3 +265,4 @@ page_actions: - [Агенты на FastNear](/agents) — полная карта поверхностей, базовые URL и подсказки по поглощению промптов. - [Аутентификация для агентов](/agents/auth) — работа с учётными данными и операционный режим. - [Плейбуки для агентов](/agents/playbooks) — примеры многошаговых сценариев. +- [Как построить MCP-сервер на FastNear](/agents/mcp) — рекомендуемый первый набор инструментов и TypeScript-пример на прямых HTTP-вызовах. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx index 4e60c6d..ff5de5f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/index.mdx @@ -19,6 +19,7 @@ page_actions: - Нужно выбрать, с какого API FastNear начать? Используйте [Как выбрать подходящую поверхность](/agents/choosing-surfaces). - Нужны правила работы с учётными данными? Используйте [Аутентификацию для агентов](/agents/auth). - Нужны примеры многошаговых сценариев? Используйте [Плейбуки для агентов](/agents/playbooks). +- Нужно отдать FastNear в Claude Desktop, Codex, Cursor или другой MCP-клиент? Используйте [Как построить MCP-сервер на FastNear](/agents/mcp). - Нужна точная документация по эндпоинту сейчас? Сразу откройте [Справочник RPC](/rpc), [FastNear API](/api), [Транзакции API](/tx), [API переводов](/transfers), [NEAR Data API](/neardata) или [KV FastData API](/fastdata/kv). ## FastNear для агентов за минуту @@ -145,3 +146,4 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" - Нужна глубина маршрутизации и компромиссы? [Как выбрать подходящую поверхность](/agents/choosing-surfaces) - Нужен режим работы с учётными данными и обращение с секретами? [Аутентификация для агентов](/agents/auth) - Нужны примеры сценариев? [Плейбуки для агентов](/agents/playbooks) +- Нужно собрать собственную MCP-поверхность поверх FastNear? [Как построить MCP-сервер на FastNear](/agents/mcp) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/agents/mcp.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/agents/mcp.mdx new file mode 100644 index 0000000..4fe2dbf --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/mcp.mdx @@ -0,0 +1,484 @@ +--- +title: "Как построить MCP-сервер на FastNear" +description: "Используйте RPC и REST API FastNear внутри собственного MCP-сервера на TypeScript с примером на прямых HTTP-вызовах." +slug: /agents/mcp +sidebar_position: 5 +displayed_sidebar: null +page_actions: + - markdown +--- + +# Как построить MCP-сервер на FastNear + +{/* FASTNEAR_AI_DISCOVERY: Эта страница предназначена для разработчиков, которые хотят отдать FastNear через собственный MCP-сервер. Здесь есть рекомендуемый стартовый набор инструментов, объяснение, почему прямой HTTP — хороший базовый выбор, и пример TypeScript, который можно сразу взять за основу. */} + +Эта страница предназначена для команд, которым нужно отдать FastNear через собственный MCP-сервер для Claude Desktop, Codex, Cursor или другого MCP-клиента. `docs.fastnear.com` сам по себе не является MCP-сервером. Цель этой страницы — показать чистую стартовую точку поверх уже существующих RPC- и REST-API FastNear. + +## Когда MCP действительно нужен + +Стройте MCP-сервер, когда нужен один стабильный набор инструментов поверх API FastNear: + +- данные цепочки должны быть доступны как инструменты в desktop-клиенте или IDE +- нужно, чтобы агент сам выбирал между поверхностями аккаунта, транзакций, блоков и RPC +- требуется спрятать аутентификацию и базовые URL за небольшим доверенным бэкендом или локальным процессом +- нужен переиспользуемый контракт инструментов вместо того, чтобы каждый агент или сценарий напрямую вызывал сырой HTTP + +Если ваш сценарий и так контролирует HTTP и не нуждается в обнаружении инструментов, MCP может быть лишним. Документация и API FastNear уже хорошо работают как обычные HTTP-поверхности. + +## Рекомендуемый первый набор инструментов + +Начинайте с нескольких широких инструментов, а не с зеркалирования каждого эндпоинта: + +| MCP-инструмент | Поверхность FastNear | Для чего использовать | +| --- | --- | --- | +| `get-account-summary` | [V1 Full Account View](/api/v1/account-full) | балансы, активы, стейкинг, сводки в стиле кошелька | +| `lookup-public-key` | [V1 Public Key Lookup](/api/v1/public-key) | разрешение публичного ключа в один или несколько аккаунтов | +| `get-transactions-by-hash` | [Transactions by Hash](/tx/transactions) | расследование транзакций и читаемое продолжение по исполнению | +| `get-latest-final-block` | [Last Final Block Redirect](/neardata/last-block-final) | проверки последней финализированной головы и сценарии опроса | +| `view-account-rpc` | [View Account](/rpc/account/view-account) | точное каноническое состояние аккаунта, когда индексированной сводки недостаточно | + +Такой набор покрывает большинство сценариев «что есть у этого аккаунта?», «что произошло с этой транзакцией?» и «какой сейчас последний финализированный блок?» без требования понимать всю поверхность FastNear заранее. + +## Почему пример использует прямой HTTP + +Этот пример намеренно использует сырые вызовы `fetch()`, а не `near-api-js`. + +- Документация, которую вы читаете, устроена вокруг сырых RPC- и REST-контрактов. +- Прямой HTTP удерживает поведение MCP-инструментов в точном соответствии с документацией. +- Это помогает избежать пробелов абстракции SDK, версионного дрейфа и поведения вспомогательных функций, скрывающих реальный формат запросов и ответов. +- Для MCP-инструментов с упором на чтение прямой HTTP обычно оказывается самым простым рабочим решением. + +Если позже в том же процессе понадобятся подпись транзакций, вспомогательные функции для аккаунтов или сценарии, завязанные на кошелёк, `near-api-js` всё ещё может иметь смысл. Но для обучающего примера, сфокусированного на RPC и API FastNear, прямые вызовы — более чистый выбор по умолчанию. + +## Установка + +Используйте Node.js 20 или новее, чтобы глобальный `fetch()` уже был доступен. + +```bash +mkdir fastnear-mcp +cd fastnear-mcp +npm init -y +npm install @modelcontextprotocol/sdk zod +npm install -D tsx typescript @types/node +``` + +Официальная документация MCP TypeScript SDK сейчас рекомендует стабильную ветку `v1.x` для продовых сценариев. Команда установки выше подтянет текущий стабильный релиз пакета. + +## TypeScript-пример, который можно сразу брать за основу + +Создайте `fastnear-mcp.ts`: + +```ts title="fastnear-mcp.ts" +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; + +type Network = "mainnet" | "testnet"; + +const DEFAULT_NETWORK: Network = + process.env.FASTNEAR_DEFAULT_NETWORK === "testnet" ? "testnet" : "mainnet"; + +const URLS: Record< + Network, + { + api: string; + rpc: string; + neardata: string; + tx: string; + } +> = { + mainnet: { + api: "https://api.fastnear.com", + rpc: "https://rpc.mainnet.fastnear.com", + neardata: "https://mainnet.neardata.xyz", + tx: "https://tx.main.fastnear.com", + }, + testnet: { + api: "https://test.api.fastnear.com", + rpc: "https://rpc.testnet.fastnear.com", + neardata: "https://testnet.neardata.xyz", + tx: "https://tx.test.fastnear.com", + }, +}; + +const apiKey = process.env.FASTNEAR_API_KEY; + +function selectNetwork(network?: Network): Network { + return network ?? DEFAULT_NETWORK; +} + +function authHeaders(extra: HeadersInit = {}): HeadersInit { + if (!apiKey) { + return extra; + } + + return { + ...extra, + Authorization: `Bearer ${apiKey}`, + }; +} + +async function requestJson(url: string, init?: RequestInit): Promise { + const response = await fetch(url, init); + const text = await response.text(); + + let body: unknown = null; + + if (text) { + try { + body = JSON.parse(text); + } catch { + body = text; + } + } + + if (!response.ok) { + const detail = + typeof body === "string" ? body : JSON.stringify(body, null, 2); + throw new Error(`${response.status} ${response.statusText}: ${detail}`); + } + + return body; +} + +function toolResult(data: unknown) { + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(data, null, 2), + }, + ], + }; +} + +function toolError(error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return { + isError: true, + content: [ + { + type: "text" as const, + text: message, + }, + ], + }; +} + +async function runTool(work: () => Promise) { + try { + return toolResult(await work()); + } catch (error) { + return toolError(error); + } +} + +function toIsoFromNanoseconds(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + + try { + return new Date(Number(BigInt(value) / 1_000_000n)).toISOString(); + } catch { + return null; + } +} + +async function getAccountSummary(network: Network, accountId: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/account/${encodeURIComponent(accountId)}/full`, + { + headers: authHeaders(), + }, + ); +} + +async function lookupPublicKey(network: Network, publicKey: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/public_key/${encodeURIComponent(publicKey)}`, + { + headers: authHeaders(), + }, + ); +} + +async function getTransactionsByHash(network: Network, txHashes: string[]) { + const baseUrl = URLS[network].tx; + return requestJson(`${baseUrl}/v0/transactions`, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + tx_hashes: txHashes, + }), + }); +} + +async function getLatestFinalBlock(network: Network) { + const baseUrl = URLS[network].neardata; + const result = (await requestJson(`${baseUrl}/v0/last_block/final`, { + headers: authHeaders(), + })) as { + block?: { + author?: string; + chunks?: unknown[]; + header?: { + height?: number; + hash?: string; + prev_hash?: string; + timestamp_nanosec?: string; + }; + }; + }; + + const block = result.block; + const header = block?.header; + + return { + network, + source: "NEAR Data API", + finality: "final", + block_height: header?.height ?? null, + block_hash: header?.hash ?? null, + prev_block_hash: header?.prev_hash ?? null, + author: block?.author ?? null, + timestamp_nanosec: header?.timestamp_nanosec ?? null, + timestamp_iso: toIsoFromNanoseconds(header?.timestamp_nanosec), + chunk_count: Array.isArray(block?.chunks) ? block.chunks.length : null, + }; +} + +async function viewAccountRpc( + network: Network, + accountId: string, + finality: "final" | "optimistic", +) { + const rpcUrl = URLS[network].rpc; + + return requestJson(rpcUrl, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + jsonrpc: "2.0", + id: `view-account:${accountId}`, + method: "query", + params: { + request_type: "view_account", + finality, + account_id: accountId, + }, + }), + }); +} + +const server = new McpServer({ + name: "fastnear-direct-http", + version: "0.1.0", +}); + +server.registerTool( + "get-account-summary", + { + title: "Get account summary", + description: + "Fetch a combined FastNear account view with balances, assets, and staking data.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, network }) => + runTool(() => getAccountSummary(selectNetwork(network), accountId)), +); + +server.registerTool( + "lookup-public-key", + { + title: "Lookup public key", + description: + "Resolve a public key to one or more NEAR accounts using the FastNear API.", + inputSchema: { + publicKey: z + .string() + .min(16) + .describe("Public key, for example ed25519:..."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ publicKey, network }) => + runTool(() => lookupPublicKey(selectNetwork(network), publicKey)), +); + +server.registerTool( + "get-transactions-by-hash", + { + title: "Get transactions by hash", + description: + "Fetch up to 20 transactions by hash from the Transactions API.", + inputSchema: { + txHashes: z + .array(z.string().min(32)) + .min(1) + .max(20) + .describe("One or more base58 transaction hashes."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ txHashes, network }) => + runTool(() => getTransactionsByHash(selectNetwork(network), txHashes)), +); + +server.registerTool( + "get-latest-final-block", + { + title: "Get latest final block", + description: + "Fetch a compact summary of the latest finalized block from the NEAR Data API.", + inputSchema: { + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ network }) => + runTool(() => getLatestFinalBlock(selectNetwork(network))), +); + +server.registerTool( + "view-account-rpc", + { + title: "View account via RPC", + description: + "Fetch canonical account state directly from NEAR JSON-RPC.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + finality: z + .enum(["final", "optimistic"]) + .optional() + .describe("Defaults to final."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, finality, network }) => + runTool(() => + viewAccountRpc( + selectNetwork(network), + accountId, + finality ?? "final", + ), + ), +); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); +``` + +Локальный запуск: + +```bash +FASTNEAR_API_KEY=your_key_here \ +FASTNEAR_DEFAULT_NETWORK=mainnet \ +npx tsx fastnear-mcp.ts +``` + +`FASTNEAR_API_KEY` для многих публичных чтений не обязателен, но для аутентифицированных рантаймов и трафика с повышенными лимитами это правильный режим по умолчанию. + +Вспомогательный маршрут `NEAR Data API` по пути `/v0/last_block/final` отвечает редиректом на маршрут текущего блока. Обычный `fetch()` проходит этот редирект автоматически, поэтому в примере не нужен отдельный код для обработки перенаправления. + +## Универсальная конфигурация клиента + +Эту страницу лучше держать универсальной. У большинства локальных MCP-клиентов одни и те же основные составляющие, даже если путь к конфигу или форма JSON немного отличаются: `command`, `args`, `env` и иногда `cwd`. + +Многие локальные MCP-клиенты принимают что-то очень похожее: + +```json title="Пример конфигурации MCP-клиента" +{ + "mcpServers": { + "fastnear": { + "command": "npx", + "args": ["tsx", "/absolute/path/to/fastnear-mcp.ts"], + "env": { + "FASTNEAR_API_KEY": "your_key_here", + "FASTNEAR_DEFAULT_NETWORK": "mainnet" + } + } + } +} +``` + +Если MCP-клиент запускает команды из другого рабочего каталога, задайте его опцию `cwd` или `workingDirectory`, если она поддерживается, либо замените `npx tsx` на абсолютный путь к локальному бинарнику `tsx`. Важно, чтобы клиент мог разрешить локально установленный пакет `tsx` до запуска `fastnear-mcp.ts`. + +Одного универсального примера конфигурации для такой страницы обычно достаточно. Фрагменты конфигурации под конкретные продукты и кнопки в духе «open in ...» меняются быстрее, чем сам контракт MCP-инструментов. + +Если позже понадобится удалённое или командное развёртывание, начните с этой же поверхности инструментов и только потом переходите от `stdio` к сетевому транспорту, когда действительно понадобится удалённый сервер. + +## Чеклист проектирования инструментов + +Когда вы превращаете FastNear в MCP-инструменты, эти значения по умолчанию обычно оказываются устойчивыми: + +- Называйте инструменты по задачам пользователя, а не по путям эндпоинтов. +- Начинайте с трёх-шести инструментов, а не с пятидесяти. +- Используйте [FastNear API](/api) для сводок и задач разрешения идентификаторов, [Transactions API](/tx) для читаемой истории, а [Справочник RPC](/rpc) — только когда важна каноническая семантика протокола. +- Делайте `network` опциональным, но явным, с разумным значением по умолчанию. +- Возвращайте компактный JSON. Не тяните огромные пэйлоады, если инструменту нужен только один срез ответа. +- Для инструментов, работающих в режиме опроса, вроде маршрутов по последнему блоку, по умолчанию лучше возвращать сводку, а не полное тело блока. +- Храните API-ключи в переменных окружения или менеджере секретов и подставляйте их на стороне сервера. +- Сохраняйте непрозрачные токены пагинации ровно в том виде, в каком их вернул FastNear. +- Ясно сообщайте вызывающей стороне, возвращает ли инструмент индексированную сводку или канонические данные RPC. +- Делайте так, чтобы один инструмент отвечал за один тип ответа. Не создавайте несколько перекрывающихся инструментов, которые частично решают одну и ту же задачу. + +## Частые ошибки + +- Не выставляйте каждый эндпоинт FastNear как отдельный MCP-инструмент в первый же день. +- Не заставляйте сценарии со сводкой по аккаунту идти через сырой RPC, если индексированное API уже отвечает на реальный вопрос пользователя. +- Не скрывайте разницу между индексированными данными и каноническими данными узла. +- Не кладите `FASTNEAR_API_KEY` в промпты, браузерное хранилище или закоммиченный конфиг. +- Не превращайте `NEAR Data API` в фальшивую потоковую абстракцию. Это поверхность чтения, ориентированная на явный опрос. + +## Полезные следующие расширения + +После того как базовый сервер заработал, следующими полезными инструментами обычно становятся: + +- история аккаунта поверх [Transactions API](/tx/account) +- история только переводов поверх [Transfers API](/transfers/query) +- `view`-вызовы контрактов поверх [Call a Function](/rpc/contract/call-function) +- небольшой `resource` или `prompt` с правилами маршрутизации и аутентификации именно вашей команды + +## Связанные руководства + +- [Агенты на FastNear](/agents) +- [Как выбрать подходящую поверхность](/agents/choosing-surfaces) +- [Аутентификация для агентов](/agents/auth) +- [Плейбуки для агентов](/agents/playbooks) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/agents/playbooks.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/agents/playbooks.mdx index 2799ae5..06f685e 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/agents/playbooks.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/agents/playbooks.mdx @@ -267,4 +267,5 @@ page_actions: - используйте [Как выбрать подходящую поверхность](/agents/choosing-surfaces), чтобы выбрать первый API - используйте [Аутентификацию для агентов](/agents/auth), если блокер — работа с учётными данными +- используйте [Как построить MCP-сервер на FastNear](/agents/mcp), если вы проектируете переиспользуемую MCP-поверхность, а не отвечаете на одну пользовательскую задачу - возвращайтесь к [Агентам на FastNear](/agents) за правилами рабочего цикла по умолчанию и формы ответа diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/broadcast-tx-commit.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/broadcast-tx-commit.mdx index 252eae1..2f939c4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/broadcast-tx-commit.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/broadcast-tx-commit.mdx @@ -12,5 +12,6 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `broadcast_tx_commit` — тип запроса. +Для каждого запроса нужна свежеподписанная транзакция в формате base64. Этот устаревший синхронный способ по-прежнему задокументирован, но для новых интеграций предпочтителен `send_tx`, а показанный пример полезной нагрузки носит лишь иллюстративный характер. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/send-tx.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/send-tx.mdx index 4cc7bf8..55b7073 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/send-tx.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/rpc/transaction/send-tx.mdx @@ -12,5 +12,6 @@ import FastnearDirectOperation from '@site/src/components/FastnearDirectOperatio `send_tx` — тип запроса. +Для каждого запроса нужна свежеподписанная транзакция в формате base64. Пример полезной нагрузки в интерактивной документации показан только для ориентира и не сработает, если вставить его без изменений. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx index 4e33d25..bf9fd2c 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/index.mdx @@ -2,7 +2,7 @@ sidebar_position: 2 slug: /snapshots title: "Снапшоты для валидаторов" -description: "Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR." +description: "Текущий статус снапшотов FastNear и рекомендации для подъёма узлов NEAR и запросов архивных снапшотов." sidebar_label: "Обзор снапшотов" displayed_sidebar: snapshotsSidebars keywords: @@ -21,50 +21,51 @@ import SimpleButton from '@site/src/components/SimpleButton'; Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. :::warning[Бесплатные снапшоты устарели] -Бесплатные снапшоты данных nearcore больше не выпускаются. +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на NEAR Nodes. ::: -## Используйте этот раздел, когда - -- нужно поднять узел mainnet или testnet из данных снапшота -- идёт восстановление RPC- или архивного узла -- уже известно, что нужен путь загрузки снапшота FastNear - -## Не используйте этот раздел, когда +## Текущий статус -- идёт запрос данных цепочки для приложения -- нужны свежие блоки, балансы, история или состояние контракта -- нужны общие рекомендации по продуктовому API, а не настройка оператором +- Сейчас FastNear не публикует через этот сайт рабочий публичный путь для самостоятельной загрузки снапшотов. +- Для обычного запуска RPC-узлов и валидаторов используйте NEAR Nodes и актуальную схему Epoch Sync вместе с децентрализованной синхронизацией состояния. +- Для доступа к архивным снапшотам переходите на страницу FastNear о снапшотах или пишите на почту команды FastNear по снапшотам. +- Исторические скрипты из fastnear/static по-прежнему существуют, но прежний публичный поток с `latest.txt` больше не является поддерживаемым публичным сценарием. -В этих случаях используйте [Справочник RPC](/rpc), [FastNear API](/api), [Транзакции API](/tx) или [NEAR Data API](/neardata). +## Полезные материалы для операторов -## Перед загрузкой +- [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync) — актуальный шаг для обновления списка boot nodes перед первым запуском. +- [NEAR Nodes: синхронизация состояния](https://near-nodes.io/rpc/state-sync) — текущая рекомендуемая схема запуска без снапшота. +- [NEAR Nodes: запуск архивного узла](https://near-nodes.io/archival/run-archival-node) — настройка архивного узла и ожидания по каталогу данных. +- [NEAR Nodes: раздельное хранилище для архивных узлов](https://near-nodes.io/archival/split-storage-archival) — если вы планируете современную схему с раздельным горячим и холодным хранилищем. -- Сначала выберите сеть: mainnet или testnet. -- Решите, нужны обычные данные RPC или архивные. -- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. +## Что стоит подготовить до запроса архивного снапшота -- Установите `rclone` — скрипты загрузки от него зависят. +- сеть: `mainnet` или `testnet` +- роль узла: валидатор, RPC-узел или архивный узел +- ожидаете ли вы восстановление в единый каталог `~/.near/data` или в раздельную схему с горячим и холодным хранилищем +- целевой путь `NEAR_HOME` и схему хранилища, которую вы собираетесь использовать +- срочность, ограничения по пропускной способности или сроки восстановления, которые влияют на план передачи данных -:::info[Установка `rclone`] -Установите `rclone` командой: +## Используйте этот раздел, когда -```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash -``` -::: +- нужно подтвердить текущую позицию FastNear по снапшотам для mainnet или testnet +- нужен правильный путь запроса архивного снапшота +- нужно понять, стоит ли использовать снапшоты или стандартный сценарий запуска узла NEAR -## Что покрывает каждый путь +## Не используйте этот раздел, когда -- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. -- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. +- идёт запрос данных цепочки для приложения +- нужны свежие блоки, балансы, история или состояние контракта +- нужны общие рекомендации по продуктовому API, а не настройка оператором -Требования к узлам смотрите в nearcore, а исходники скриптов загрузки, которые используются в этих руководствах, — в fastnear/static. +В этих случаях используйте [Справочник RPC](/rpc), [FastNear API](/api), [Транзакции API](/tx) или [NEAR Data API](/neardata). ## Выберите сеть +На страницах ниже собран текущий статус и путь эскалации по каждой сети. Они больше не предполагают наличие публичного URL для самостоятельной загрузки. +
Снапшоты mainnet Снапшоты testnet diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/mainnet.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/mainnet.mdx index bdac1c4..b1a7093 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/mainnet.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/mainnet.mdx @@ -1,134 +1,46 @@ --- sidebar_label: "Mainnet" slug: /snapshots/mainnet -description: "Скачайте RPC- и архивные снапшоты mainnet для быстрого развёртывания NEAR-инфраструктуры на базе FastNear." +description: "Текущая позиция FastNear по снапшотам mainnet для RPC-подъёма и запросов архивных снапшотов." --- # Mainnet -## Оптимизированный снапшот mainnet +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота mainnet. -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и подходит для более узких задач. - -Узлы с достаточными ресурсами могут использовать значение `$RPC_TYPE=fast-rpc`. По умолчанию используется `rpc`. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `RPC_TYPE=fast-rpc` — включает оптимизированный режим -::: - -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## RPC-снапшот mainnet - -Это стандартный способ получить снапшот без оптимизированного режима из предыдущего раздела. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet bash -``` - -## Архивный снапшот mainnet - -:::warning -**Требует много времени и места на диске.** - -Подготовьтесь к очень большому объёму загрузки и длительному времени выполнения. - -Размер снапшота составляет около 60 ТБ, и он содержит более 1 миллиона файлов. -::: +## Для обычных RPC-узлов и валидаторов -Перед запуском скрипта загрузки можно задать следующие переменные окружения: +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов mainnet. -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -По умолчанию скрипт ожидает следующие пути для данных: +## Для архивных снапшотов mainnet -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` -- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** +## Что важно знать о старых скриптах -1. Получите высоту блока последнего снапшота: +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -`Latest archival mainnet snapshot block`: +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +## Если вы готовитесь к запросу архивного снапшота -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. +- Будьте готовы разнести hot data и cold data, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: - -`Archival Mainnet Snapshot (hot-data) » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` - -3. Скачайте данные COLD из снапшота. Их можно разместить на HDD. - -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=cold-data` — выбирает загрузку Cold data -- `DATA_PATH=/mnt/hdds/cold-data` — путь для размещения cold data. **Обратите внимание:** конфигурация nearcore должна указывать на тот же путь для cold data. -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +## Если FastNear передаст вам пакет снапшота -`Archival Mainnet Snapshot (cold-data) » /mnt/hdds/cold-data`: +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=cold-data DATA_PATH=/mnt/hdds/cold-data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/testnet.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/testnet.mdx index c1a3175..6cfe284 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/testnet.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/snapshots/testnet.mdx @@ -1,83 +1,46 @@ --- sidebar_label: "Testnet" slug: /snapshots/testnet -description: "Скачайте RPC- и архивные снапшоты testnet для быстрого развёртывания NEAR-инфраструктуры на базе FastNear." +description: "Текущая позиция FastNear по снапшотам testnet для RPC-подъёма и запросов архивных снапшотов." --- # Testnet -## RPC-снапшот testnet +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота testnet. -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и нужен для более узких задач. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот testnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает данные testnet +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -`RPC Testnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=testnet bash -``` +## Для обычных RPC-узлов и валидаторов -## Архивный снапшот testnet +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов testnet. -:::warning -**Требует много времени и места на диске.** +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -Подготовьтесь к большому объёму загрузки и длительному времени выполнения. -::: - -Перед запуском скрипта загрузки можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +## Для архивных снапшотов testnet -По умолчанию скрипт ожидает следующий путь для данных: +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` +## Что важно знать о старых скриптах -**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -1. Получите высоту блока последнего снапшота: +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -`Latest archival testnet snapshot block`: +## Если вы готовитесь к запросу архивного снапшота -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +- Будьте готовы разместить hot data на быстром локальном хранилище, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. - -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает сеть testnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +## Если FastNear передаст вам пакет снапшота -`Archival Testnet Snapshot (hot-data) » ~/.near/data`: +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=testnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/package.json b/package.json index 66f6136..b398add 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "audit:i18n:ru": "node scripts/audit-ru-translation.js", "audit:ru-terminology": "node scripts/audit-ru-terminology.js", "audit:indexing": "yarn test:route-semantics && yarn audit:page-models && yarn build && node scripts/audit-indexing-surface.js", + "audit:agent-readiness:live": "node scripts/audit-agent-readiness-live.js", "audit:algolia-highlights": "node scripts/audit-algolia-highlights.js", "audit:algolia-relevance": "node scripts/audit-algolia-relevance.js", "algolia:inspect": "node scripts/algolia-inspect.js", diff --git a/scripts/audit-agent-readiness-live.js b/scripts/audit-agent-readiness-live.js new file mode 100644 index 0000000..bf7b344 --- /dev/null +++ b/scripts/audit-agent-readiness-live.js @@ -0,0 +1,93 @@ +#!/usr/bin/env node + +const SITE_ORIGIN = (process.env.SITE_ORIGIN || "https://docs.fastnear.com").replace(/\/+$/, ""); + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +async function fetchText(url, init) { + const response = await fetch(url, init); + const text = await response.text(); + return { response, text }; +} + +async function assertHeadDiscoveryHeaders(pathname, expectedServiceDocPath) { + const response = await fetch(`${SITE_ORIGIN}${pathname}`, { method: "HEAD" }); + assert(response.ok, `HEAD ${pathname} failed with ${response.status}`); + + const link = response.headers.get("link") || ""; + [ + 'rel="api-catalog"', + 'rel="service-doc"', + 'rel="service-meta"', + ].forEach((fragment) => { + assert(link.includes(fragment), `HEAD ${pathname} is missing ${fragment} in Link headers`); + }); + assert( + link.includes(`<${expectedServiceDocPath}>`) || link.includes(``), + `HEAD ${pathname} should advertise ${expectedServiceDocPath} as service-doc` + ); +} + +async function main() { + await assertHeadDiscoveryHeaders("/", "/agents"); + await assertHeadDiscoveryHeaders("/ru", "/ru/agents"); + + const apiCatalog = await fetch(`${SITE_ORIGIN}/.well-known/api-catalog`); + assert(apiCatalog.ok, `GET /.well-known/api-catalog failed with ${apiCatalog.status}`); + const apiCatalogType = apiCatalog.headers.get("content-type") || ""; + assert( + apiCatalogType.includes("application/linkset+json"), + "API catalog must return application/linkset+json" + ); + const apiCatalogJson = await apiCatalog.json(); + assert(Array.isArray(apiCatalogJson.linkset), "API catalog must expose a linkset array"); + + const indexResponse = await fetch(`${SITE_ORIGIN}/.well-known/agent-skills/index.json`); + assert( + indexResponse.ok, + `GET /.well-known/agent-skills/index.json failed with ${indexResponse.status}` + ); + const indexType = indexResponse.headers.get("content-type") || ""; + assert(indexType.includes("application/json"), "Agent Skills index must return JSON"); + const indexJson = await indexResponse.json(); + assert(Array.isArray(indexJson.skills), "Agent Skills index must expose a skills array"); + + for (const skill of indexJson.skills) { + const skillResponse = await fetch(new URL(skill.url, `${SITE_ORIGIN}/`)); + assert(skillResponse.ok, `GET ${skill.url} failed with ${skillResponse.status}`); + const skillType = skillResponse.headers.get("content-type") || ""; + assert( + skillType.includes("text/markdown"), + `Skill ${skill.name} must return text/markdown` + ); + } + + const { response: markdownResponse, text: markdownText } = await fetchText(`${SITE_ORIGIN}/`, { + headers: { + Accept: "text/markdown", + }, + }); + assert(markdownResponse.ok, `GET / as markdown failed with ${markdownResponse.status}`); + const markdownType = markdownResponse.headers.get("content-type") || ""; + assert(markdownType.includes("text/markdown"), "GET / with Accept: text/markdown must return markdown"); + assert( + !/ { + console.error(`Live agent-readiness smoke checks failed: ${error.message}`); + process.exitCode = 1; +}); diff --git a/scripts/audit-indexing-surface.js b/scripts/audit-indexing-surface.js index 9ec480e..26f24a5 100644 --- a/scripts/audit-indexing-surface.js +++ b/scripts/audit-indexing-surface.js @@ -1,5 +1,6 @@ #!/usr/bin/env node +const crypto = require("node:crypto"); const fs = require("node:fs"); const path = require("node:path"); const { @@ -41,13 +42,65 @@ const ALGOLIA_RELEVANCE_AUDIT_PATH = path.join(ROOT, "scripts/audit-algolia-rele const SEARCH_BAR_PATH = path.join(ROOT, "src/theme/SearchBar/index.js"); const SEARCH_BAR_RUNTIME_PATH = path.join(ROOT, "src/theme/SearchBar/AlgoliaSearchRuntime.js"); const SEARCH_BAR_STYLES_PATH = path.join(ROOT, "src/theme/SearchBar/styles.css"); +const HEADERS_PATH = path.join(BUILD_ROOT, "_headers"); const REDIRECTS_PATH = path.join(BUILD_ROOT, "_redirects"); const ROBOTS_PATH = path.join(BUILD_ROOT, "robots.txt"); +const API_CATALOG_PATH = path.join(BUILD_ROOT, ".well-known", "api-catalog"); +const AGENT_SKILLS_INDEX_PATH = path.join( + BUILD_ROOT, + ".well-known", + "agent-skills", + "index.json" +); const PAGE_MODELS_PATH = path.join(ROOT, "src/data/generatedFastnearPageModels.json"); const STRUCTURED_GRAPH_PATH = path.join(ROOT, "src/data/generatedFastnearStructuredGraph.json"); const PRODUCTION_SITE_URL = "https://docs.fastnear.com"; const WEBSITE_ID = `${PRODUCTION_SITE_URL}/#website`; const ORGANIZATION_ID = `${PRODUCTION_SITE_URL}/#organization`; +const EXPECTED_CONTENT_SIGNAL = "Content-Signal: search=yes, ai-input=yes, ai-train=yes"; +const EXPECTED_ROOT_LINK_HEADERS = [ + 'Link: ; rel="api-catalog"; type="application/linkset+json"', + 'Link: ; rel="service-doc"; type="text/html"', + 'Link: ; rel="service-meta"; type="application/json"', + 'Link: ; rel="service-meta"; type="application/json"', +]; +const EXPECTED_RU_LINK_HEADERS = [ + 'Link: ; rel="api-catalog"; type="application/linkset+json"', + 'Link: ; rel="service-doc"; type="text/html"', + 'Link: ; rel="service-meta"; type="application/json"', + 'Link: ; rel="service-meta"; type="application/json"', +]; +const EXPECTED_HEADER_RULES = [ + { + path: "/", + lines: EXPECTED_ROOT_LINK_HEADERS, + }, + { + path: "/ru", + lines: EXPECTED_RU_LINK_HEADERS, + }, + { + path: "/.well-known/api-catalog", + lines: [ + 'Content-Type: application/linkset+json; charset=utf-8; profile="https://www.rfc-editor.org/info/rfc9727"', + 'Link: ; rel="api-catalog"; type="application/linkset+json"', + ], + }, + { + path: "/.well-known/agent-skills/index.json", + lines: ['Content-Type: application/json; charset=utf-8'], + }, + { + path: "/.well-known/agent-skills/*/SKILL.md", + lines: ['Content-Type: text/markdown; charset=utf-8'], + }, +]; +const EXPECTED_API_CATALOG_ANCHORS = [ + "https://api.fastnear.com", + "https://mainnet.neardata.xyz", + "https://rpc.mainnet.fastnear.com", +]; +const EXPECTED_AGENT_SKILLS = ["auth", "overview", "playbooks", "surface-routing"]; const hideEarlyApiFamilies = /^(1|true|yes|on)$/i.test( process.env.HIDE_EARLY_API_FAMILIES || "" @@ -117,6 +170,10 @@ function loadJson(filePath, label) { return JSON.parse(fs.readFileSync(filePath, "utf8")); } +function sha256File(filePath) { + return `sha256:${crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex")}`; +} + function walkDocs(dirPath) { const collected = []; @@ -707,6 +764,101 @@ function parseSitemapUrls(locale = DEFAULT_LOCALE) { ); } +function auditAgentDiscoveryArtifacts() { + assert(fs.existsSync(HEADERS_PATH), `Missing _headers: ${path.relative(ROOT, HEADERS_PATH)}`); + const headersText = fs.readFileSync(HEADERS_PATH, "utf8"); + EXPECTED_HEADER_RULES.forEach(({ path: rulePath, lines }) => { + assert(headersText.includes(`${rulePath}\n`), `_headers is missing the ${rulePath} rule`); + lines.forEach((line) => { + assert( + headersText.includes(line), + `_headers is missing "${line}" for ${rulePath}` + ); + }); + }); + + assert( + fs.existsSync(API_CATALOG_PATH), + `Missing API catalog: ${path.relative(ROOT, API_CATALOG_PATH)}` + ); + const apiCatalog = loadJson(API_CATALOG_PATH, "API catalog"); + assert(Array.isArray(apiCatalog.linkset), "API catalog must expose a linkset array"); + const apiCatalogAnchors = apiCatalog.linkset.map((entry) => entry.anchor).sort(); + assert( + JSON.stringify(apiCatalogAnchors) === JSON.stringify(EXPECTED_API_CATALOG_ANCHORS), + `API catalog anchors are out of sync. Expected ${EXPECTED_API_CATALOG_ANCHORS.join(", ")}, got ${apiCatalogAnchors.join(", ")}` + ); + apiCatalog.linkset.forEach((entry) => { + ["service-desc", "service-doc", "status"].forEach((relation) => { + assert( + Array.isArray(entry[relation]) && entry[relation].length > 0, + `API catalog entry ${entry.anchor} must include ${relation}` + ); + entry[relation].forEach((target) => { + assert( + typeof target.href === "string" && /^https:\/\//.test(target.href), + `API catalog entry ${entry.anchor} has an invalid ${relation} target` + ); + }); + }); + }); + [ + "/openapi/fastnear.json", + "/openapi/neardata.json", + ].forEach((route) => { + const filePath = routeToBuildAssetPath(route); + assert( + fs.existsSync(filePath), + `Build is missing the vendored OpenAPI snapshot ${route}` + ); + }); + + assert( + fs.existsSync(AGENT_SKILLS_INDEX_PATH), + `Missing Agent Skills index: ${path.relative(ROOT, AGENT_SKILLS_INDEX_PATH)}` + ); + const agentSkillsIndex = loadJson(AGENT_SKILLS_INDEX_PATH, "Agent Skills index"); + assert( + agentSkillsIndex.$schema === "https://schemas.agentskills.io/discovery/0.2.0/schema.json", + "Agent Skills index must use the v0.2.0 schema URI" + ); + assert(Array.isArray(agentSkillsIndex.skills), "Agent Skills index must expose a skills array"); + const skillNames = agentSkillsIndex.skills.map((skill) => skill.name).sort(); + assert( + JSON.stringify(skillNames) === JSON.stringify(EXPECTED_AGENT_SKILLS), + `Agent Skills index names are out of sync. Expected ${EXPECTED_AGENT_SKILLS.join(", ")}, got ${skillNames.join(", ")}` + ); + agentSkillsIndex.skills.forEach((skill) => { + assert(skill.type === "skill-md", `Agent skill ${skill.name} must use type=skill-md`); + assert( + typeof skill.description === "string" && skill.description.length > 0, + `Agent skill ${skill.name} must include a description` + ); + assert( + typeof skill.url === "string" && skill.url.startsWith("/.well-known/agent-skills/"), + `Agent skill ${skill.name} must use a stable well-known URL` + ); + const filePath = routeToBuildAssetPath(skill.url); + assert( + fs.existsSync(filePath), + `Agent skill artifact is missing: ${path.relative(ROOT, filePath)}` + ); + assert( + skill.digest === sha256File(filePath), + `Agent skill digest mismatch for ${skill.name}` + ); + const content = fs.readFileSync(filePath, "utf8"); + assert( + content.startsWith("---\n"), + `Agent skill ${skill.name} must include YAML frontmatter` + ); + assert( + content.includes(`name: "${skill.name}"`) || content.includes(`name: '${skill.name}'`), + `Agent skill ${skill.name} frontmatter must declare its name` + ); + }); +} + function auditDocsBuildOutput(routeEntries, structuredGraph) { const pageModels = loadJson(PAGE_MODELS_PATH, "generated FastNear page models"); const pageModelsById = Object.fromEntries( @@ -732,6 +884,11 @@ function auditDocsBuildOutput(routeEntries, structuredGraph) { `robots.txt must explicitly allow ${userAgent}` ); }); + assert( + robotsText.includes(EXPECTED_CONTENT_SIGNAL), + "robots.txt must declare the agreed Content-Signal policy" + ); + auditAgentDiscoveryArtifacts(); assert(fs.existsSync(REDIRECTS_PATH), `Missing redirects file: ${path.relative(ROOT, REDIRECTS_PATH)}`); const redirectsText = fs.readFileSync(REDIRECTS_PATH, "utf8"); diff --git a/scripts/generate-ai-surfaces.js b/scripts/generate-ai-surfaces.js index 27e4464..bffe456 100644 --- a/scripts/generate-ai-surfaces.js +++ b/scripts/generate-ai-surfaces.js @@ -1,5 +1,6 @@ #!/usr/bin/env node +const crypto = require("crypto"); const fs = require("fs"); const path = require("path"); const { isSecretQueryParam } = require("../src/utils/fastnearOperationUrlState"); @@ -28,6 +29,7 @@ const { const ROOT = path.resolve(__dirname, ".."); const DOCS_ROOT = path.resolve(ROOT, "docs"); const PAGE_MODELS_PATH = path.resolve(ROOT, "src/data/generatedFastnearPageModels.json"); +const OPENAPI_SNAPSHOTS_ROOT = path.resolve(ROOT, "src/data/openapiSnapshots"); const STRUCTURED_GRAPH_PATH = path.resolve( ROOT, "src/data/generatedFastnearStructuredGraph.json" @@ -38,12 +40,14 @@ const WEBSITE_ID = `${SITE_ORIGIN}/#website`; const ORGANIZATION_ID = `${SITE_ORIGIN}/#organization`; const ORGANIZATION_LOGO_URL = `${SITE_ORIGIN}/img/fastnear_logo_black.png`; const ORGANIZATION_SAME_AS = ["https://github.com/fastnear", "https://x.com/fast_near"]; +const AGENT_SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema.json"; const hideEarlyApiFamilies = /^(1|true|yes|on)$/i.test( process.env.HIDE_EARLY_API_FAMILIES || "" ); const GENERATED_STATIC_ROOTS = [ + path.join(STATIC_ROOT, ".well-known"), path.join(STATIC_ROOT, "docs"), path.join(STATIC_ROOT, "guides"), path.join(STATIC_ROOT, "rpc"), @@ -58,6 +62,7 @@ const GENERATED_STATIC_ROOTS = [ path.join(STATIC_ROOT, "transaction-flow"), path.join(STATIC_ROOT, "rpcs"), path.join(STATIC_ROOT, "apis"), + path.join(STATIC_ROOT, "openapi"), path.join(STATIC_ROOT, "structured-data"), ]; const GENERATED_STATIC_FILES = [ @@ -65,6 +70,107 @@ const GENERATED_STATIC_FILES = [ path.join(STATIC_ROOT, "llms.txt"), path.join(STATIC_ROOT, "llms-full.txt"), ]; +const OPENAPI_SNAPSHOT_FILES = { + fastnear: { + filePath: path.join(OPENAPI_SNAPSHOTS_ROOT, "fastnear.json"), + publishedPath: "/openapi/fastnear.json", + }, + neardata: { + filePath: path.join(OPENAPI_SNAPSHOTS_ROOT, "neardata.json"), + publishedPath: "/openapi/neardata.json", + }, +}; +const AGENT_SKILL_DEFINITIONS = [ + { + description: + "Operational entry point for FastNear agents. Use when you need the default workflow, discovery surfaces, and the first API to reach for.", + name: "overview", + route: "/agents", + }, + { + description: + "Surface-selection guide for FastNear. Use when you need to route a task to RPC, FastNear API, history APIs, NEAR Data, FastData, or snapshots.", + name: "surface-routing", + route: "/agents/choosing-surfaces", + }, + { + description: + "Authentication guidance for FastNear agents. Use when a task involves API keys, secret handling, or request authorization posture.", + name: "auth", + route: "/agents/auth", + }, + { + description: + "Common FastNear playbooks for agents. Use when you need the default multi-step workflow for holdings, transaction tracing, transfers, block monitoring, storage, or snapshots.", + name: "playbooks", + route: "/agents/playbooks", + }, +]; +const API_CATALOG_ENTRIES = [ + { + anchor: "https://rpc.mainnet.fastnear.com", + "service-desc": [ + { + href: "https://rpc.mainnet.fastnear.com/openapi.json", + type: "application/json", + }, + ], + "service-doc": [ + { + href: `${SITE_ORIGIN}/rpc`, + type: "text/html", + }, + ], + status: [ + { + href: "https://status.fastnear.com/status/main", + type: "text/html", + }, + ], + }, + { + anchor: "https://api.fastnear.com", + "service-desc": [ + { + href: `${SITE_ORIGIN}${OPENAPI_SNAPSHOT_FILES.fastnear.publishedPath}`, + type: "application/json", + }, + ], + "service-doc": [ + { + href: `${SITE_ORIGIN}/api`, + type: "text/html", + }, + ], + status: [ + { + href: "https://api.fastnear.com/status", + type: "application/json", + }, + ], + }, + { + anchor: "https://mainnet.neardata.xyz", + "service-desc": [ + { + href: `${SITE_ORIGIN}${OPENAPI_SNAPSHOT_FILES.neardata.publishedPath}`, + type: "application/json", + }, + ], + "service-doc": [ + { + href: `${SITE_ORIGIN}/neardata`, + type: "text/html", + }, + ], + status: [ + { + href: "https://mainnet.neardata.xyz/health", + type: "application/json", + }, + ], + }, +]; const API_SERVICE_LABELS = { en: { @@ -296,6 +402,10 @@ function writeTextFile(filePath, content) { fs.writeFileSync(filePath, content, "utf8"); } +function writeJsonFile(filePath, value) { + writeTextFile(filePath, `${JSON.stringify(value, null, 2)}\n`); +} + function normalizeRoute(route) { if (!route) { return "/"; @@ -638,6 +748,7 @@ function transformInlineLinks(markdown, locale = DEFAULT_LOCALE) { function transformSimpleJsx(markdown, locale = DEFAULT_LOCALE) { let transformed = markdown + .replace(/\{\/\*[\s\S]*?\*\/\}/g, "") .replace(/<\/?React\.Fragment[^>]*>/g, "") .replace(//g, "") .replace(/([\s\S]*?)<\/strong>/g, "**$1**"); @@ -1616,6 +1727,83 @@ function buildSiteGraphArtifact({ authoredDocEntries, canonicalEntries, docEntri }; } +function copyOpenApiSnapshots() { + Object.values(OPENAPI_SNAPSHOT_FILES).forEach(({ filePath, publishedPath }) => { + if (!fs.existsSync(filePath)) { + throw new Error(`Missing vendored OpenAPI snapshot: ${path.relative(ROOT, filePath)}`); + } + + const snapshot = JSON.parse(fs.readFileSync(filePath, "utf8")); + writeJsonFile(path.join(STATIC_ROOT, publishedPath.replace(/^\//, "")), snapshot); + }); +} + +function buildApiCatalogArtifact() { + return { + linkset: API_CATALOG_ENTRIES, + }; +} + +function quoteYamlString(value) { + return JSON.stringify(String(value)); +} + +function buildAgentSkillContent({ description, name, markdown }) { + const frontmatter = [ + "---", + `name: ${quoteYamlString(name)}`, + `description: ${quoteYamlString(description)}`, + "---", + "", + ].join("\n"); + + return `${frontmatter}${markdown}`; +} + +function computeSha256Digest(content) { + return `sha256:${crypto.createHash("sha256").update(content, "utf8").digest("hex")}`; +} + +function buildAgentSkillsArtifacts(entries) { + const entriesByRoute = Object.fromEntries(entries.map((entry) => [entry.route, entry])); + const skills = AGENT_SKILL_DEFINITIONS.map((definition) => { + const entry = entriesByRoute[definition.route]; + if (!entry) { + throw new Error(`Missing authored docs entry for agent skill route ${definition.route}`); + } + + const url = `/.well-known/agent-skills/${definition.name}/SKILL.md`; + const content = buildAgentSkillContent({ + description: definition.description, + name: definition.name, + markdown: entry.markdown, + }); + + return { + content, + description: definition.description, + digest: computeSha256Digest(content), + name: definition.name, + type: "skill-md", + url, + }; + }); + + return { + index: { + $schema: AGENT_SKILLS_SCHEMA, + skills: skills.map(({ description, digest, name, type, url }) => ({ + description, + digest, + name, + type, + url, + })), + }, + skills, + }; +} + function writeMirrorEntries(entries) { for (const entry of entries) { const markdownPaths = entry.markdownPaths || [entry.markdownPath]; @@ -1690,6 +1878,7 @@ function buildFullArchive(entries, locale = DEFAULT_LOCALE) { function main() { removeGeneratedStaticRoots(); + copyOpenApiSnapshots(); for (const locale of SUPPORTED_LOCALES) { const labels = getAuthoredMarkdownLabels(locale); @@ -1770,6 +1959,22 @@ function main() { )}\n` ); + if (locale === DEFAULT_LOCALE) { + writeJsonFile( + path.join(STATIC_ROOT, ".well-known/api-catalog"), + buildApiCatalogArtifact() + ); + + const agentSkills = buildAgentSkillsArtifacts(authoredDocEntries); + writeJsonFile( + path.join(STATIC_ROOT, ".well-known/agent-skills/index.json"), + agentSkills.index + ); + agentSkills.skills.forEach((skill) => { + writeTextFile(path.join(STATIC_ROOT, skill.url.replace(/^\//, "")), skill.content); + }); + } + if (wrapperDocEntries.length === 0) { throw new Error("Expected docs operation wrapper pages to generate Markdown mirrors."); } diff --git a/src/data/openapiSnapshots/fastnear.json b/src/data/openapiSnapshots/fastnear.json new file mode 100644 index 0000000..6c84c03 --- /dev/null +++ b/src/data/openapiSnapshots/fastnear.json @@ -0,0 +1,1324 @@ +{ + "components": { + "schemas": { + "AccountBalanceRow": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "balance": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "account_id", + "balance" + ], + "type": "object" + }, + "AccountFullResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "nfts": { + "items": { + "$ref": "#/components/schemas/NftRow" + }, + "type": "array" + }, + "pools": { + "items": { + "$ref": "#/components/schemas/PoolRow" + }, + "type": "array" + }, + "state": { + "allOf": [ + { + "$ref": "#/components/schemas/AccountStateResponse" + } + ], + "nullable": true, + "type": "object" + }, + "tokens": { + "items": { + "$ref": "#/components/schemas/TokenRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "pools", + "tokens", + "nfts", + "state" + ], + "type": "object" + }, + "AccountStateResponse": { + "additionalProperties": false, + "properties": { + "balance": { + "nullable": true, + "type": "string" + }, + "locked": { + "nullable": true, + "type": "string" + }, + "storage_bytes": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "required": [ + "balance", + "locked", + "storage_bytes" + ], + "type": "object" + }, + "HealthResponse": { + "additionalProperties": false, + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "NftRow": { + "additionalProperties": false, + "properties": { + "contract_id": { + "type": "string" + }, + "last_update_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "required": [ + "contract_id", + "last_update_block_height" + ], + "type": "object" + }, + "PoolRow": { + "additionalProperties": false, + "properties": { + "last_update_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "pool_id": { + "type": "string" + } + }, + "required": [ + "pool_id", + "last_update_block_height" + ], + "type": "object" + }, + "PublicKeyLookupResponse": { + "additionalProperties": false, + "properties": { + "account_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "public_key": { + "type": "string" + } + }, + "required": [ + "public_key", + "account_ids" + ], + "type": "object" + }, + "StatusResponse": { + "additionalProperties": false, + "properties": { + "sync_balance_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "sync_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "sync_block_timestamp_nanosec": { + "nullable": true, + "type": "string" + }, + "sync_latency_sec": { + "format": "double", + "nullable": true, + "type": "number" + }, + "version": { + "type": "string" + } + }, + "required": [ + "version", + "sync_block_height", + "sync_latency_sec", + "sync_block_timestamp_nanosec", + "sync_balance_block_height" + ], + "type": "object" + }, + "TokenAccountsResponse": { + "additionalProperties": false, + "properties": { + "accounts": { + "items": { + "$ref": "#/components/schemas/AccountBalanceRow" + }, + "type": "array" + }, + "token_id": { + "type": "string" + } + }, + "required": [ + "token_id", + "accounts" + ], + "type": "object" + }, + "TokenRow": { + "additionalProperties": false, + "properties": { + "balance": { + "nullable": true, + "type": "string" + }, + "contract_id": { + "type": "string" + }, + "last_update_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "required": [ + "contract_id", + "last_update_block_height", + "balance" + ], + "type": "object" + }, + "V0ContractsResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "contract_ids": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "contract_ids" + ], + "type": "object" + }, + "V0StakingResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "pools": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "pools" + ], + "type": "object" + }, + "V1FtResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "tokens": { + "items": { + "$ref": "#/components/schemas/TokenRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "tokens" + ], + "type": "object" + }, + "V1NftResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "tokens": { + "items": { + "$ref": "#/components/schemas/NftRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "tokens" + ], + "type": "object" + }, + "V1StakingResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "pools": { + "items": { + "$ref": "#/components/schemas/PoolRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "pools" + ], + "type": "object" + } + } + }, + "info": { + "description": "Low-latency indexed account, token, and public-key lookup APIs for wallets and explorers. Embedded portal clients may forward an optional `apiKey` query parameter, but the public FastNEAR API does not require it.", + "title": "FastNEAR API", + "version": "3.0.3" + }, + "openapi": "3.0.3", + "paths": { + "/health": { + "get": { + "description": "Ping the FastNEAR API for liveness \u2014 returns `{status: ok}` when healthy.", + "operationId": "get_health", + "parameters": [ + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "status": "ok" + }, + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + }, + "description": "Health status string" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Get service health", + "tags": [ + "system" + ], + "x-fastnear-slug": "health", + "x-fastnear-title": "FastNEAR API - Health" + } + }, + "/status": { + "get": { + "description": "Check the current indexed block height, latency, and deployed service version.", + "operationId": "get_status", + "parameters": [ + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "sync_balance_block_height": 129734103, + "sync_block_height": 129734103, + "sync_block_timestamp_nanosec": "1728256282197171397", + "sync_latency_sec": 4.671730603, + "version": "0.10.0" + }, + "schema": { + "$ref": "#/components/schemas/StatusResponse" + } + } + }, + "description": "Current FastNEAR API sync status" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Get service sync status", + "tags": [ + "system" + ], + "x-fastnear-slug": "status", + "x-fastnear-title": "FastNEAR API - Status" + } + }, + "/v0/account/{account_id}/ft": { + "get": { + "description": "Fetch the fungible token contract IDs an account has held \u2014 contract IDs only, no balances.", + "operationId": "account_ft_v0", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "here.tg", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "here.tg", + "contract_ids": [ + "wrap.near", + "usdt.tether-token.near" + ] + }, + "schema": { + "$ref": "#/components/schemas/V0ContractsResponse" + } + } + }, + "description": "Fungible token contract IDs for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup fungible token contract IDs for an account", + "tags": [ + "fungible-tokens" + ], + "x-fastnear-slug": "account_ft", + "x-fastnear-title": "FastNEAR API - V0 Account FT" + } + }, + "/v0/account/{account_id}/nft": { + "get": { + "description": "Fetch the NFT contract IDs an account has held \u2014 contract IDs only, no block-height metadata.", + "operationId": "account_nft_v0", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "sharddog.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "sharddog.near", + "contract_ids": [ + "nft.example.near" + ] + }, + "schema": { + "$ref": "#/components/schemas/V0ContractsResponse" + } + } + }, + "description": "NFT contract IDs for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup NFT contract IDs for an account", + "tags": [ + "non-fungible-tokens" + ], + "x-fastnear-slug": "account_nft", + "x-fastnear-title": "FastNEAR API - V0 Account NFT" + } + }, + "/v0/account/{account_id}/staking": { + "get": { + "description": "Fetch staking pool account IDs for one account \u2014 pool IDs only, no block-height metadata.", + "operationId": "account_staking_v0", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "mob.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "mob.near", + "pools": [ + "zavodil.poolv1.near" + ] + }, + "schema": { + "$ref": "#/components/schemas/V0StakingResponse" + } + } + }, + "description": "Staking pool account IDs for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup staking pool account IDs for an account", + "tags": [ + "staking" + ], + "x-fastnear-slug": "account_staking", + "x-fastnear-title": "FastNEAR API - V0 Account Staking" + } + }, + "/v0/public_key/{public_key}": { + "get": { + "description": "Fetch the account IDs that have registered a full-access public key, via the legacy V0 lookup path.", + "operationId": "lookup_by_public_key_v0", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied full-access public key" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup full-access accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup", + "x-fastnear-title": "FastNEAR API - V0 Public Key Lookup" + } + }, + "/v0/public_key/{public_key}/all": { + "get": { + "description": "List every account tied to a public key \u2014 full-access and limited-access keys together \u2014 via the legacy V0 lookup-all path.", + "operationId": "lookup_by_public_key_all_v0", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied public key, including limited-access keys" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup all indexed accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup_all", + "x-fastnear-title": "FastNEAR API - V0 Public Key Lookup (All)" + } + }, + "/v1/account/{account_id}/ft": { + "get": { + "description": "Fetch an account's fungible token balance rows, each with contract ID, balance, and last-update block height.", + "operationId": "account_ft_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "here.tg", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "here.tg", + "tokens": [ + { + "balance": "1000000000000000000000000", + "contract_id": "wrap.near", + "last_update_block_height": null + } + ] + }, + "schema": { + "$ref": "#/components/schemas/V1FtResponse" + } + } + }, + "description": "Indexed fungible token rows for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup indexed fungible token rows for an account", + "tags": [ + "fungible-tokens" + ], + "x-fastnear-slug": "account_ft", + "x-fastnear-title": "FastNEAR API - V1 Account FT" + } + }, + "/v1/account/{account_id}/full": { + "get": { + "description": "Fetch the combined indexed account view, including staking pools, FT balances, NFTs, and account state.", + "operationId": "account_full_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "here.tg", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "here.tg", + "nfts": [], + "pools": [], + "state": { + "balance": "1000000000000000000000000", + "locked": "0", + "storage_bytes": 512 + }, + "tokens": [] + }, + "schema": { + "$ref": "#/components/schemas/AccountFullResponse" + } + } + }, + "description": "Full indexed account information for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup full indexed account information", + "tags": [ + "accounts" + ], + "x-fastnear-slug": "account_full", + "x-fastnear-title": "FastNEAR API - V1 Account Full" + } + }, + "/v1/account/{account_id}/nft": { + "get": { + "description": "Fetch NFT contract rows for an account, including block-height metadata for each contract.", + "operationId": "account_nft_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "sharddog.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "sharddog.near", + "tokens": [ + { + "contract_id": "nft.example.near", + "last_update_block_height": null + } + ] + }, + "schema": { + "$ref": "#/components/schemas/V1NftResponse" + } + } + }, + "description": "Indexed NFT contract rows for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup indexed NFT contract rows for an account", + "tags": [ + "non-fungible-tokens" + ], + "x-fastnear-slug": "account_nft", + "x-fastnear-title": "FastNEAR API - V1 Account NFT" + } + }, + "/v1/account/{account_id}/staking": { + "get": { + "description": "Retrieve staking pool rows for an account, including block-height metadata for each pool relationship.", + "operationId": "account_staking_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "mob.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "mob.near", + "pools": [ + { + "last_update_block_height": null, + "pool_id": "zavodil.poolv1.near" + } + ] + }, + "schema": { + "$ref": "#/components/schemas/V1StakingResponse" + } + } + }, + "description": "Indexed staking pool rows for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup indexed staking pools for an account", + "tags": [ + "staking" + ], + "x-fastnear-slug": "account_staking", + "x-fastnear-title": "FastNEAR API - V1 Account Staking" + } + }, + "/v1/ft/{token_id}/top": { + "get": { + "description": "Fetch the top-balance holder list for a fungible token contract, ranked highest balance first.", + "operationId": "ft_top_v1", + "parameters": [ + { + "description": "Fungible token contract account ID.", + "example": "wrap.near", + "in": "path", + "name": "token_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "accounts": [ + { + "account_id": "mob.near", + "balance": "979894691374420631019486155" + } + ], + "token_id": "wrap.near" + }, + "schema": { + "$ref": "#/components/schemas/TokenAccountsResponse" + } + } + }, + "description": "Indexed top holders for the requested fungible token" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup top indexed holders for a fungible token", + "tags": [ + "fungible-tokens" + ], + "x-fastnear-slug": "ft_top", + "x-fastnear-title": "FastNEAR API - V1 FT Top Holders" + } + }, + "/v1/public_key/{public_key}": { + "get": { + "description": "Resolve a full-access public key to the indexed NEAR accounts that have registered it \u2014 V1 canonical lookup path.", + "operationId": "lookup_by_public_key_v1", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied full-access public key" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup full-access accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup", + "x-fastnear-title": "FastNEAR API - V1 Public Key Lookup" + } + }, + "/v1/public_key/{public_key}/all": { + "get": { + "description": "Resolve a public key to every account that holds it \u2014 full-access and limited-access keys alike \u2014 via the V1 lookup-all path.", + "operationId": "lookup_by_public_key_all_v1", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied public key, including limited-access keys" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup all indexed accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup_all", + "x-fastnear-title": "FastNEAR API - V1 Public Key Lookup (All)" + } + } + }, + "servers": [ + { + "description": "Mainnet", + "url": "https://api.fastnear.com" + }, + { + "description": "Testnet", + "url": "https://test.api.fastnear.com" + } + ] +} diff --git a/src/data/openapiSnapshots/neardata.json b/src/data/openapiSnapshots/neardata.json new file mode 100644 index 0000000..b5dbbbd --- /dev/null +++ b/src/data/openapiSnapshots/neardata.json @@ -0,0 +1,1536 @@ +{ + "components": { + "schemas": { + "ActionDocument": { + "additionalProperties": true, + "type": "object" + }, + "ActionReceiptBody": { + "additionalProperties": false, + "properties": { + "Action": { + "$ref": "#/components/schemas/ActionReceiptDocument" + } + }, + "required": [ + "Action" + ], + "type": "object" + }, + "ActionReceiptDocument": { + "additionalProperties": false, + "properties": { + "actions": { + "items": { + "$ref": "#/components/schemas/ActionDocument" + }, + "type": "array" + }, + "gas_price": { + "type": "string" + }, + "input_data_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "is_promise_yield": { + "type": "boolean" + }, + "output_data_receivers": { + "items": { + "$ref": "#/components/schemas/OutputDataReceiverDocument" + }, + "type": "array" + }, + "signer_id": { + "type": "string" + }, + "signer_public_key": { + "type": "string" + } + }, + "required": [ + "actions", + "gas_price", + "input_data_ids", + "is_promise_yield", + "output_data_receivers", + "signer_id", + "signer_public_key" + ], + "type": "object" + }, + "BlockDocument": { + "additionalProperties": false, + "description": "Full block document as served by neardata, including the block envelope and per-shard payloads.", + "properties": { + "block": { + "$ref": "#/components/schemas/BlockEnvelope" + }, + "shards": { + "items": { + "$ref": "#/components/schemas/ShardDocument" + }, + "type": "array" + } + }, + "required": [ + "block", + "shards" + ], + "type": "object" + }, + "BlockEnvelope": { + "additionalProperties": false, + "description": "Block-level payload returned by neardata.", + "properties": { + "author": { + "description": "Block producer account ID.", + "type": "string" + }, + "chunks": { + "items": { + "$ref": "#/components/schemas/ChunkHeader" + }, + "type": "array" + }, + "header": { + "$ref": "#/components/schemas/BlockHeader" + } + }, + "required": [ + "author", + "chunks", + "header" + ], + "type": "object" + }, + "BlockErrorResponse": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/BlockErrorType" + } + }, + "required": [ + "error", + "type" + ], + "type": "object" + }, + "BlockErrorType": { + "enum": [ + "BLOCK_HEIGHT_TOO_HIGH", + "BLOCK_HEIGHT_TOO_LOW", + "BLOCK_DOES_NOT_EXIST" + ], + "type": "string" + }, + "BlockHeader": { + "additionalProperties": true, + "description": "Block header object as served by neardata.", + "properties": { + "chunks_included": { + "format": "uint64", + "type": "integer" + }, + "epoch_id": { + "type": "string" + }, + "gas_price": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "height": { + "format": "uint64", + "type": "integer" + }, + "next_epoch_id": { + "type": "string" + }, + "prev_hash": { + "type": "string" + }, + "prev_height": { + "format": "uint64", + "type": "integer" + }, + "timestamp": { + "type": "integer" + }, + "timestamp_nanosec": { + "type": "string" + }, + "total_supply": { + "type": "string" + } + }, + "type": "object" + }, + "ChunkDocument": { + "additionalProperties": false, + "description": "Chunk payload returned by neardata for a single shard in a selected block.", + "properties": { + "author": { + "description": "Chunk producer account ID.", + "type": "string" + }, + "header": { + "$ref": "#/components/schemas/ChunkHeader" + }, + "receipts": { + "items": { + "$ref": "#/components/schemas/ReceiptDocument" + }, + "type": "array" + }, + "transactions": { + "items": { + "$ref": "#/components/schemas/ChunkTransactionWrapper" + }, + "type": "array" + } + }, + "required": [ + "author", + "header", + "receipts", + "transactions" + ], + "type": "object" + }, + "ChunkHeader": { + "additionalProperties": true, + "description": "Chunk header object as served by neardata.", + "properties": { + "chunk_hash": { + "type": "string" + }, + "gas_limit": { + "type": "integer" + }, + "gas_used": { + "type": "integer" + }, + "height_created": { + "format": "uint64", + "type": "integer" + }, + "height_included": { + "format": "uint64", + "type": "integer" + }, + "outcome_root": { + "type": "string" + }, + "outgoing_receipts_root": { + "type": "string" + }, + "prev_block_hash": { + "type": "string" + }, + "shard_id": { + "format": "uint64", + "type": "integer" + }, + "tx_root": { + "type": "string" + } + }, + "type": "object" + }, + "ChunkTransactionWrapper": { + "additionalProperties": false, + "description": "Transaction entry returned inside a neardata chunk.", + "properties": { + "outcome": { + "$ref": "#/components/schemas/ExecutionWithReceipt" + }, + "transaction": { + "$ref": "#/components/schemas/SignedTransactionDocument" + } + }, + "required": [ + "outcome", + "transaction" + ], + "type": "object" + }, + "DataReceiptBody": { + "additionalProperties": false, + "properties": { + "Data": { + "$ref": "#/components/schemas/DataReceiptDocument" + } + }, + "required": [ + "Data" + ], + "type": "object" + }, + "DataReceiptDocument": { + "additionalProperties": false, + "properties": { + "data": { + "type": "string" + }, + "data_id": { + "type": "string" + }, + "is_promise_resume": { + "type": "boolean" + } + }, + "required": [ + "data", + "data_id", + "is_promise_resume" + ], + "type": "object" + }, + "ExecutionOutcomeDocument": { + "additionalProperties": false, + "properties": { + "block_hash": { + "type": "string" + }, + "id": { + "type": "string" + }, + "outcome": { + "$ref": "#/components/schemas/ExecutionOutcomeSummary" + }, + "proof": { + "items": { + "$ref": "#/components/schemas/ExecutionProofItem" + }, + "type": "array" + } + }, + "required": [ + "block_hash", + "id", + "outcome", + "proof" + ], + "type": "object" + }, + "ExecutionOutcomeStatus": { + "oneOf": [ + { + "$ref": "#/components/schemas/ExecutionOutcomeStatusSuccessReceiptId" + }, + { + "$ref": "#/components/schemas/ExecutionOutcomeStatusSuccessValue" + }, + { + "$ref": "#/components/schemas/ExecutionOutcomeStatusFailure" + } + ] + }, + "ExecutionOutcomeStatusFailure": { + "additionalProperties": false, + "properties": { + "Failure": { + "additionalProperties": true, + "type": "object" + } + }, + "required": [ + "Failure" + ], + "type": "object" + }, + "ExecutionOutcomeStatusSuccessReceiptId": { + "additionalProperties": false, + "properties": { + "SuccessReceiptId": { + "type": "string" + } + }, + "required": [ + "SuccessReceiptId" + ], + "type": "object" + }, + "ExecutionOutcomeStatusSuccessValue": { + "additionalProperties": false, + "properties": { + "SuccessValue": { + "type": "string" + } + }, + "required": [ + "SuccessValue" + ], + "type": "object" + }, + "ExecutionOutcomeSummary": { + "additionalProperties": false, + "properties": { + "executor_id": { + "type": "string" + }, + "gas_burnt": { + "format": "uint64", + "type": "integer" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "type": "object" + }, + "receipt_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "$ref": "#/components/schemas/ExecutionOutcomeStatus" + }, + "tokens_burnt": { + "type": "string" + } + }, + "required": [ + "executor_id", + "gas_burnt", + "logs", + "metadata", + "receipt_ids", + "status", + "tokens_burnt" + ], + "type": "object" + }, + "ExecutionProofItem": { + "additionalProperties": true, + "type": "object" + }, + "ExecutionWithReceipt": { + "additionalProperties": false, + "description": "Execution result paired with an optional receipt object.", + "properties": { + "execution_outcome": { + "$ref": "#/components/schemas/ExecutionOutcomeDocument" + }, + "receipt": { + "description": "Receipt payload when neardata includes it for this entry.", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/ReceiptDocument" + }, + { + "$ref": "#/components/schemas/OmittedReceiptDocument" + } + ], + "type": "object" + }, + "tx_hash": { + "type": "string" + } + }, + "required": [ + "execution_outcome", + "receipt" + ], + "type": "object" + }, + "HealthResponse": { + "additionalProperties": false, + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "OmittedReceiptDocument": { + "additionalProperties": false, + "type": "object" + }, + "OutputDataReceiverDocument": { + "additionalProperties": false, + "properties": { + "data_id": { + "type": "string" + }, + "receiver_id": { + "type": "string" + } + }, + "required": [ + "data_id", + "receiver_id" + ], + "type": "object" + }, + "ReceiptBody": { + "oneOf": [ + { + "$ref": "#/components/schemas/ActionReceiptBody" + }, + { + "$ref": "#/components/schemas/DataReceiptBody" + } + ] + }, + "ReceiptDocument": { + "additionalProperties": false, + "description": "Receipt object as served by neardata inside a chunk payload.", + "properties": { + "predecessor_id": { + "type": "string" + }, + "priority": { + "format": "uint64", + "type": "integer" + }, + "receipt": { + "$ref": "#/components/schemas/ReceiptBody" + }, + "receipt_id": { + "type": "string" + }, + "receiver_id": { + "type": "string" + } + }, + "required": [ + "predecessor_id", + "priority", + "receipt", + "receipt_id", + "receiver_id" + ], + "type": "object" + }, + "ShardDocument": { + "additionalProperties": false, + "description": "Per-shard payload returned by neardata for a block.", + "properties": { + "chunk": { + "$ref": "#/components/schemas/ChunkDocument" + }, + "receipt_execution_outcomes": { + "items": { + "$ref": "#/components/schemas/ExecutionWithReceipt" + }, + "type": "array" + }, + "shard_id": { + "format": "uint64", + "type": "integer" + }, + "state_changes": { + "items": { + "$ref": "#/components/schemas/StateChangeItem" + }, + "type": "array" + } + }, + "required": [ + "chunk", + "receipt_execution_outcomes", + "shard_id", + "state_changes" + ], + "type": "object" + }, + "SignedTransactionDocument": { + "additionalProperties": false, + "properties": { + "actions": { + "items": { + "$ref": "#/components/schemas/ActionDocument" + }, + "type": "array" + }, + "hash": { + "type": "string" + }, + "nonce": { + "format": "uint64", + "type": "integer" + }, + "priority_fee": { + "format": "uint64", + "type": "integer" + }, + "public_key": { + "type": "string" + }, + "receiver_id": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "signer_id": { + "type": "string" + } + }, + "required": [ + "actions", + "hash", + "nonce", + "priority_fee", + "public_key", + "receiver_id", + "signature", + "signer_id" + ], + "type": "object" + }, + "StateChangeCause": { + "oneOf": [ + { + "$ref": "#/components/schemas/StateChangeCauseTransactionProcessing" + }, + { + "$ref": "#/components/schemas/StateChangeCauseReceiptProcessing" + }, + { + "$ref": "#/components/schemas/StateChangeCauseActionReceiptGasReward" + } + ] + }, + "StateChangeCauseActionReceiptGasReward": { + "additionalProperties": false, + "properties": { + "receipt_hash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "receipt_hash", + "type" + ], + "type": "object" + }, + "StateChangeCauseReceiptProcessing": { + "additionalProperties": false, + "properties": { + "receipt_hash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "receipt_hash", + "type" + ], + "type": "object" + }, + "StateChangeCauseTransactionProcessing": { + "additionalProperties": false, + "properties": { + "tx_hash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "tx_hash", + "type" + ], + "type": "object" + }, + "StateChangeItem": { + "additionalProperties": false, + "description": "State change entry returned by neardata for a shard.", + "properties": { + "cause": { + "$ref": "#/components/schemas/StateChangeCause" + }, + "change": { + "$ref": "#/components/schemas/StateChangeValue" + }, + "type": { + "type": "string" + } + }, + "required": [ + "cause", + "change", + "type" + ], + "type": "object" + }, + "StateChangeValue": { + "oneOf": [ + { + "$ref": "#/components/schemas/StateChangeValueAccountUpdate" + }, + { + "$ref": "#/components/schemas/StateChangeValueAccessKeyUpdate" + }, + { + "$ref": "#/components/schemas/StateChangeValueDataUpdate" + }, + { + "$ref": "#/components/schemas/StateChangeValueDataDeletion" + } + ] + }, + "StateChangeValueAccessKeyUpdate": { + "additionalProperties": false, + "properties": { + "access_key": { + "additionalProperties": true, + "type": "object" + }, + "account_id": { + "type": "string" + }, + "public_key": { + "type": "string" + } + }, + "required": [ + "access_key", + "account_id", + "public_key" + ], + "type": "object" + }, + "StateChangeValueAccountUpdate": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "code_hash": { + "type": "string" + }, + "locked": { + "type": "string" + }, + "storage_paid_at": { + "format": "uint64", + "type": "integer" + }, + "storage_usage": { + "format": "uint64", + "type": "integer" + } + }, + "required": [ + "account_id", + "amount", + "code_hash", + "locked", + "storage_paid_at", + "storage_usage" + ], + "type": "object" + }, + "StateChangeValueDataDeletion": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "key_base64": { + "type": "string" + } + }, + "required": [ + "account_id", + "key_base64" + ], + "type": "object" + }, + "StateChangeValueDataUpdate": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "key_base64": { + "type": "string" + }, + "value_base64": { + "type": "string" + } + }, + "required": [ + "account_id", + "key_base64", + "value_base64" + ], + "type": "object" + } + } + }, + "info": { + "description": "Cached and archived NEAR block data with redirect helpers for first-block and latest-block workflows. Some block-family routes may redirect depending on archive or freshness topology.", + "title": "NEAR Data API", + "version": "3.0.3" + }, + "openapi": "3.0.3", + "paths": { + "/health": { + "get": { + "description": "Ping the neardata service for liveness \u2014 returns `{status: ok}` when healthy, errors otherwise.", + "operationId": "get_health", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "status": "ok" + }, + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + }, + "description": "Health payload" + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Get service health", + "tags": [ + "system" + ], + "x-fastnear-slug": "health", + "x-fastnear-title": "NEAR Data API - Health" + } + }, + "/v0/block/{block_height}": { + "get": { + "description": "Fetch a finalized block's full document at a chosen height \u2014 header plus every chunk and shard payload.", + "operationId": "get_block", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch a finalized block by height", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block", + "x-fastnear-title": "NEAR Data API - Block" + } + }, + "/v0/block/{block_height}/chunk/{shard_id}": { + "get": { + "description": "Fetch one chunk \u2014 a single shard's transactions and incoming receipts \u2014 at a chosen block height.", + "operationId": "get_chunk", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Shard ID whose chunk should be returned.", + "example": "0", + "in": "path", + "name": "shard_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChunkDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch one chunk from a finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_chunk", + "x-fastnear-title": "NEAR Data API - Block Chunk" + } + }, + "/v0/block/{block_height}/headers": { + "get": { + "description": "Fetch only a finalized block's header and chunk summaries \u2014 no per-shard payload.", + "operationId": "get_block_headers", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockEnvelope", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch the block-level object for a finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_headers", + "x-fastnear-title": "NEAR Data API - Block Headers" + } + }, + "/v0/block/{block_height}/shard/{shard_id}": { + "get": { + "description": "Fetch one shard's full payload at a chosen block \u2014 chunk plus state changes and produced receipts.", + "operationId": "get_shard", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Shard ID to return.", + "example": "0", + "in": "path", + "name": "shard_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShardDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch one shard from a finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_shard", + "x-fastnear-title": "NEAR Data API - Block Shard" + } + }, + "/v0/block_opt/{block_height}": { + "get": { + "description": "Fetch an optimistic (not-yet-final) block at a chosen height \u2014 may redirect once the optimistic window has finalized.", + "operationId": "get_block_optimistic", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch an optimistic block by height", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_optimistic", + "x-fastnear-title": "NEAR Data API - Optimistic Block" + } + }, + "/v0/first_block": { + "get": { + "description": "Redirect to the chain's first post-genesis block \u2014 a starting cursor for indexers backfilling from the beginning.", + "operationId": "get_first_block", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Full block document returned after automatic redirect following" + }, + "302": { + "description": "Redirect to the canonical first block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + } + }, + "summary": "Redirect to the first block after genesis", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "first_block", + "x-fastnear-title": "NEAR Data API - First Block" + } + }, + "/v0/last_block/final": { + "get": { + "description": "Redirect to the most recent finalized block \u2014 the chain-tip cursor once consensus has settled.", + "operationId": "get_last_block_final", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Full block document returned after automatic redirect following" + }, + "302": { + "description": "Redirect to the latest block block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Redirect to the latest finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "last_block_final", + "x-fastnear-title": "NEAR Data API - Last Final Block" + } + }, + "/v0/last_block/optimistic": { + "get": { + "description": "Redirect to the most recent optimistic block \u2014 the freshest-possible tip, ahead of final settlement.", + "operationId": "get_last_block_optimistic", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Full block document returned after automatic redirect following" + }, + "302": { + "description": "Redirect to the latest block block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Redirect to the latest optimistic block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "last_block_optimistic", + "x-fastnear-title": "NEAR Data API - Last Optimistic Block" + } + } + }, + "servers": [ + { + "description": "Mainnet", + "url": "https://mainnet.neardata.xyz" + }, + { + "description": "Testnet", + "url": "https://testnet.neardata.xyz" + } + ] +} diff --git a/static/.well-known/agent-skills/auth/SKILL.md b/static/.well-known/agent-skills/auth/SKILL.md new file mode 100644 index 0000000..67351d6 --- /dev/null +++ b/static/.well-known/agent-skills/auth/SKILL.md @@ -0,0 +1,100 @@ +--- +name: "auth" +description: "Authentication guidance for FastNear agents. Use when a task involves API keys, secret handling, or request authorization posture." +--- +**Source:** [https://docs.fastnear.com/agents/auth](https://docs.fastnear.com/agents/auth) + +# Auth for Agents + +Agents should authenticate to FastNear the same way production backends do. Do not copy the browser-demo posture used by the docs UI into an agent, worker, or automation runtime. + +One FastNear API key works across the RPC and API endpoints. Many public reads still work without a key. For agents, the important question is not whether auth exists. It is where the credential lives, how it gets attached to requests, and how to avoid leaking it into prompts, logs, or browser state. + +## If you only need the rule + +- Store the key in an env var or secret manager. +- Inject it server-side or from the worker runtime. +- Prefer the `Authorization: Bearer ...` header. +- Apply the same key and transport rules to both regular and archival RPC hosts. +- Never ask a user to paste a FastNear key into chat, a prompt, or a browser-only agent. + +## Recommended runtime patterns + +Use one of these patterns: + +- **Server-side worker or automation**: load the key from env vars or a secret manager and attach it directly to outbound FastNear requests. +- **Thin backend proxy**: if the user-facing app runs in the browser, send the request to your backend first and let the backend inject the FastNear credential. +- **Multi-tenant service**: keep per-tenant keys in a proper secrets store and make the agent select the right credential by tenant or project context. + +Avoid browser-only agent architectures that need the FastNear key in client-side storage. + +## Choose the credential transport + +| Transport | Use it when... | Notes | +| --- | --- | --- | +| `Authorization: Bearer ${API_KEY}` | you control the HTTP client or backend | Best default for agents. Less likely to leak into URL logs, analytics, or copied links. | +| `?apiKey=${API_KEY}` | you are using simple curl or a system that cannot easily set headers | Still valid, but URLs tend to travel further through logs and tooling. Use it intentionally. | + +If you have a choice, use the header form. + +## Minimum secure flow + +1. Read the key from an env var or secret manager at runtime. +2. Attach it to the request as a header or query parameter. +3. Keep prompts, traces, and logs scrubbed so the raw key never lands in transcripts. +4. Rotate the key if it appears in a prompt, debug trace, browser storage, or a copied URL. + +Example: + +```js +const apiKey = process.env.FASTNEAR_API_KEY; + +const response = await fetch('https://rpc.mainnet.fastnear.com', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'block', + params: {finality: 'final'}, + }), +}); +``` + +## When auth is missing + +Many public FastNear endpoints are still readable without a key. If the agent can answer the user's question from public traffic, do that. + +When a key is required for higher limits, paid access, or authenticated traffic: + +- tell the user to create or retrieve a key from [dashboard.fastnear.com](https://dashboard.fastnear.com) +- ask them to configure it in an env var, secret manager, or backend configuration +- do not ask them to paste the raw key into chat so the agent can carry it around + +If the agent cannot access the configured secret, it should say that clearly and stop rather than improvising insecure storage. + +## Do not do this + +- Do not lift a key out of browser `localStorage` and treat it as an agent credential. +- Do not embed keys into browser-delivered agent apps. +- Do not keep keys in prompts, notebook cells, or plaintext config checked into source control. +- Do not prefer `?apiKey=` just because it is shorter if your infrastructure logs full URLs aggressively. + +## What the agent should tell a user + +When auth is relevant, a useful agent answer usually contains: + +- whether the current request can proceed unauthenticated +- whether the user needs to configure a FastNear API key next +- where that key should live, usually env vars, a secret manager, or a backend proxy +- which transport the agent is using, usually `Authorization: Bearer ...` + +## Related guides + +- [Auth & Access](https://docs.fastnear.com/auth) +- [Agents on FastNear](https://docs.fastnear.com/agents) +- [Choosing the Right Surface](https://docs.fastnear.com/agents/choosing-surfaces) +- [Building an MCP Server with FastNear](https://docs.fastnear.com/agents/mcp) diff --git a/static/.well-known/agent-skills/index.json b/static/.well-known/agent-skills/index.json new file mode 100644 index 0000000..3b70cac --- /dev/null +++ b/static/.well-known/agent-skills/index.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json", + "skills": [ + { + "description": "Operational entry point for FastNear agents. Use when you need the default workflow, discovery surfaces, and the first API to reach for.", + "digest": "sha256:252edd45e10606e710587d97ca82974403fb05d519784fd549dd6435a75e3209", + "name": "overview", + "type": "skill-md", + "url": "/.well-known/agent-skills/overview/SKILL.md" + }, + { + "description": "Surface-selection guide for FastNear. Use when you need to route a task to RPC, FastNear API, history APIs, NEAR Data, FastData, or snapshots.", + "digest": "sha256:a53a72ecae704e33d494bf95acdc24a30ec3b5f186c8f6fab1f5084851a41ed1", + "name": "surface-routing", + "type": "skill-md", + "url": "/.well-known/agent-skills/surface-routing/SKILL.md" + }, + { + "description": "Authentication guidance for FastNear agents. Use when a task involves API keys, secret handling, or request authorization posture.", + "digest": "sha256:f0657c097fd422295ce3413c486f0f1be8fca4ad0e635aef4beea8de1e04561f", + "name": "auth", + "type": "skill-md", + "url": "/.well-known/agent-skills/auth/SKILL.md" + }, + { + "description": "Common FastNear playbooks for agents. Use when you need the default multi-step workflow for holdings, transaction tracing, transfers, block monitoring, storage, or snapshots.", + "digest": "sha256:890c091eeaecac33c0046e56fe2f94aa537ffbee5640a8db16336f40366903f6", + "name": "playbooks", + "type": "skill-md", + "url": "/.well-known/agent-skills/playbooks/SKILL.md" + } + ] +} diff --git a/static/.well-known/agent-skills/overview/SKILL.md b/static/.well-known/agent-skills/overview/SKILL.md new file mode 100644 index 0000000..68657e2 --- /dev/null +++ b/static/.well-known/agent-skills/overview/SKILL.md @@ -0,0 +1,143 @@ +--- +name: "overview" +description: "Operational entry point for FastNear agents. Use when you need the default workflow, discovery surfaces, and the first API to reach for." +--- +**Source:** [https://docs.fastnear.com/agents](https://docs.fastnear.com/agents) + +# Agents on FastNear + +This page is the operational starting point for AI agents, crawlers, and automation runtimes using FastNear. The goal is simple: identify the user's actual task, choose one FastNear API first, fetch the smallest useful result, and only widen to another API when there is a clear missing piece. + +## If you only need the next step + +- Need to decide which FastNear API to start with? Use [Choosing the Right Surface](https://docs.fastnear.com/agents/choosing-surfaces). +- Need credential handling rules? Use [Auth for Agents](https://docs.fastnear.com/agents/auth). +- Need example multi-step workflows? Use [Agent Playbooks](https://docs.fastnear.com/agents/playbooks). +- Need to expose FastNear to Claude Desktop, Codex, Cursor, or another MCP client? Use [Building an MCP Server with FastNear](https://docs.fastnear.com/agents/mcp). +- Need exact endpoint docs now? Go directly to [RPC Reference](https://docs.fastnear.com/rpc), [FastNear API](https://docs.fastnear.com/api), [Transactions API](https://docs.fastnear.com/tx), [Transfers API](https://docs.fastnear.com/transfers), [NEAR Data API](https://docs.fastnear.com/neardata), or [KV FastData API](https://docs.fastnear.com/fastdata/kv). + +## FastNear for agents in one minute + +- Use indexed APIs when the user wants a product-shaped answer such as balances, holdings, account history, or transfer history. +- Use [RPC Reference](https://docs.fastnear.com/rpc) when the user needs canonical protocol-native fields, contract calls, or transaction submission. +- Use [NEAR Data API](https://docs.fastnear.com/neardata) when the question is about recent optimistic or finalized blocks and explicit polling. +- Use [Snapshots](https://docs.fastnear.com/snapshots) for operator workflows, not application-level data reads. +- One FastNear API key works across the RPC and API endpoints. +- Stop after the first sufficient answer. Do not collect from multiple APIs unless the current result is insufficient. + +## What to resolve before the first request + +Try to identify these inputs before you make a call: + +- **Network**: mainnet or testnet. +- **Primary identifier**: account ID, public key, transaction hash, receipt ID, block height/hash, contract ID plus storage key, or node/bootstrap task. +- **Answer shape**: summary, history, canonical protocol output, or operator instructions. +- **Freshness requirement**: historical, current, optimistic/latest, or finalized/latest. +- **Precision requirement**: indexed summary is acceptable, or exact canonical node semantics are required. + +If one of these is missing, make a small assumption when the likely starting API does not change. Ask a clarifying question only when the wrong API choice would materially change the result. + +## FastNear APIs at a glance + +| API | Start here when... | Typical inputs | Widen only if... | +| --- | --- | --- | --- | +| [FastNear API](https://docs.fastnear.com/api) | The user wants balances, NFTs, staking, public-key resolution, or an account summary | `account_id`, public key | The user needs exact canonical node fields | +| [RPC Reference](https://docs.fastnear.com/rpc) | The user wants canonical protocol output, contract calls, validator data, or transaction submission | `account_id`, block height/hash, method-specific params | The user also needs a higher-level summary or indexed history | +| [Transactions API](https://docs.fastnear.com/tx) | The user wants transaction, receipt, account, or block execution history | transaction hash, receipt ID, `account_id`, block identifiers | The user needs exact RPC-level follow-up or finality semantics | +| [Transfers API](https://docs.fastnear.com/transfers) | The user wants transfer-only history | `account_id`, transfer filters | The question broadens to general execution context | +| [NEAR Data API](https://docs.fastnear.com/neardata) | The user wants recent optimistic or finalized blocks, headers, or latest-block helpers | block height/hash, freshness requirement | The user needs exact canonical block/state follow-up | +| [KV FastData API](https://docs.fastnear.com/fastdata/kv) | The user wants indexed contract key history or latest indexed key-value state | contract ID, storage key | The user needs exact on-chain current state | +| [Snapshots](https://docs.fastnear.com/snapshots) | The user is standing up infrastructure | network, node type, operator goal | The user shifts to application-level chain questions | + +## Default workflow + +Use this loop unless the task clearly needs something more specialized: + +1. Translate the user's wording into the task they actually need solved. + Examples: account summary, canonical state inspection, transaction investigation, transfer-only history, recent block monitoring, or node bootstrap. +2. Pick one FastNear API first. + Do not gather from multiple APIs until the first result proves insufficient. +3. Pull the smallest relevant docs context. + Use the API index page, endpoint page, or Markdown mirror instead of broad unrelated docs. +4. Make the first request that matches the user's identifier and expected answer shape. +5. Stop if the result is already sufficient to answer the user's question. +6. Widen only when you can name the missing piece precisely. + Examples: canonical confirmation, broader execution history, fresher block-family data, or exact protocol fields. + +## Good defaults + +When the user's wording is short but the intent is common, these defaults are usually correct: + +- "Check this account" usually starts with [FastNear API](https://docs.fastnear.com/api). +- "Check this public key" usually starts with [FastNear API](https://docs.fastnear.com/api) for key-to-account resolution. +- "Check this transaction" usually starts with [Transactions API](https://docs.fastnear.com/tx). +- "Check this receipt" usually starts with [Transactions API](https://docs.fastnear.com/tx). +- "Check this block" usually starts with [NEAR Data API](https://docs.fastnear.com/neardata) for recent-block monitoring or [RPC Reference](https://docs.fastnear.com/rpc) for exact canonical block data. +- "Check this contract key/history" usually starts with [KV FastData API](https://docs.fastnear.com/fastdata/kv). +- "Help me bootstrap a node" starts with [Snapshots](https://docs.fastnear.com/snapshots). + +Full routing rules and tradeoffs live in [Choosing the Right Surface](https://docs.fastnear.com/agents/choosing-surfaces). + +## Widen carefully + +Good escalation patterns: + +- Start with [FastNear API](https://docs.fastnear.com/api), then move to [RPC Reference](https://docs.fastnear.com/rpc) if the user asks for exact canonical confirmation. +- Start with [Transactions API](https://docs.fastnear.com/tx), then move to [RPC Reference](https://docs.fastnear.com/rpc) if the user needs protocol-level transaction or receipt follow-up. +- Start with [Transfers API](https://docs.fastnear.com/transfers), then widen to [Transactions API](https://docs.fastnear.com/tx) if the user broadens the question beyond transfer events. +- Start with [NEAR Data API](https://docs.fastnear.com/neardata), then move to [RPC Reference](https://docs.fastnear.com/rpc) if the user needs exact canonical block or state inspection. + +Bad pattern: + +- Pull from several FastNear APIs before you know what the user actually needs. That usually produces a noisier answer, not a better one. + +## Authenticate once, reuse everywhere + +Public endpoints often work without a key. Add a key for higher limits, a shared authenticated posture, or paid access patterns. The same key works across every FastNear API above, including the regular and archival RPC hosts; send it either as an HTTP header or a URL parameter: + +```bash title="Authorization header" +curl "https://rpc.mainnet.fastnear.com" \ + -H "Authorization: Bearer ${API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{"method":"block","params":{"finality":"final"},"id":1,"jsonrpc":"2.0"}' +``` + +```bash title="URL parameter" +curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" +``` + +Get a key from [dashboard.fastnear.com](https://dashboard.fastnear.com). Operational posture for non-interactive runtimes: [Auth for Agents](https://docs.fastnear.com/agents/auth) — keys go in env vars or a secret manager, never in browser storage, chat logs, or prompts. Full flow and header details: [Auth & Access](https://docs.fastnear.com/auth). + +## Pull clean docs into a prompt + +- Every page has a **Copy Markdown** button in the top-right toolbar. It emits a navigation-chrome-free Markdown version of the page, ready to paste into a prompt or RAG store. +- The `llms.txt` convention is mirrored here: + - [`/llms.txt`](https://docs.fastnear.com/llms.txt) — index of pages and short descriptions. + - [`/llms-full.txt`](https://docs.fastnear.com/llms-full.txt) — the full docs corpus concatenated into one file. + - Russian-locale equivalents live at [`/ru/llms.txt`](https://docs.fastnear.com/llms.txt) and [`/ru/llms-full.txt`](https://docs.fastnear.com/llms-full.txt). +- Machine-readable site structure for graph-aware ingestion: [`/structured-data/site-graph.json`](https://docs.fastnear.com/structured-data/site-graph.json) (mirrored in `/ru/`). +- Per-page Markdown mirrors live under the same slug with a `.md` suffix if you prefer direct fetches over the Copy Markdown button. + +## Per-call hints an agent should know + +- Parameter names, response fields, and example payloads are rendered live on each endpoint page. The underlying registry lives at `src/data/generatedFastnearPageModels.json` if you are mirroring schema into another system. +- `?network=testnet` is supported on specific pages only. Each page calls out its network support in the **Auth and availability** section; do not assume it works globally. +- Pagination tokens (`resume_token`, `page_token`) are opaque. Reuse them verbatim and only with the endpoint plus filter set that produced them. +- Status routes: every REST family ships a `/status` and `/health` path for liveness and sync-latency inspection. + +## What a useful agent answer should contain + +- A brief statement of which FastNear API was used and why, especially if the choice was an inference. +- The answer in the shape the user is likely to need next: summary first for humans, exact fields or next-call guidance when the caller is technical. +- Any important caveat about freshness, canonicality, pagination, or network choice. +- A follow-up path only when it is likely to help. + Examples: "use RPC for canonical confirmation" or "use Transactions API if you need broader execution context." + +Avoid dumping raw payloads when the user is really asking for interpretation. + +## Next docs by need + +- Need routing depth and tradeoffs? [Choosing the Right Surface](https://docs.fastnear.com/agents/choosing-surfaces) +- Need credential posture and secret handling? [Auth for Agents](https://docs.fastnear.com/agents/auth) +- Need example workflows? [Agent Playbooks](https://docs.fastnear.com/agents/playbooks) +- Need to build your own MCP tool surface on top of FastNear? [Building an MCP Server with FastNear](https://docs.fastnear.com/agents/mcp) diff --git a/static/.well-known/agent-skills/playbooks/SKILL.md b/static/.well-known/agent-skills/playbooks/SKILL.md new file mode 100644 index 0000000..3b35a01 --- /dev/null +++ b/static/.well-known/agent-skills/playbooks/SKILL.md @@ -0,0 +1,265 @@ +--- +name: "playbooks" +description: "Common FastNear playbooks for agents. Use when you need the default multi-step workflow for holdings, transaction tracing, transfers, block monitoring, storage, or snapshots." +--- +**Source:** [https://docs.fastnear.com/agents/playbooks](https://docs.fastnear.com/agents/playbooks) + +# Agent Playbooks + +Use this page when the agent already knows the kind of task it is handling and needs the default next steps. Each playbook starts with one FastNear API, names the minimum useful inputs, and tells you when to stop versus when to widen. + +The core rule stays the same across all playbooks: start with one API, get the smallest useful result, and only widen when you can name the missing piece. + +## How to use these playbooks + +1. Match the user's request to the closest playbook below. +2. Gather the minimum inputs. +3. Make the first request from the suggested starting API. +4. Stop as soon as you can answer in the shape the user actually needs. +5. Widen only for a specific missing field, freshness requirement, or canonicality requirement. + +## Quick map + +| If the user wants... | Start with... | Widen only if... | +| --- | --- | --- | +| account balances, holdings, staking, or wallet-style summary | [FastNear API](https://docs.fastnear.com/api) | exact canonical node fields are required | +| transaction, receipt, or account execution history | [Transactions API](https://docs.fastnear.com/tx) | exact RPC-level status or submission semantics are required | +| transfer-only history | [Transfers API](https://docs.fastnear.com/transfers) | the question broadens beyond transfers | +| latest optimistic or finalized blocks | [NEAR Data API](https://docs.fastnear.com/neardata) | exact canonical block or state follow-up is required | +| indexed contract key state or key history | [KV FastData API](https://docs.fastnear.com/fastdata/kv) | exact current on-chain state is required | +| node bootstrap or operator setup | [Snapshots](https://docs.fastnear.com/snapshots) | the task shifts back to application-level chain data | + +If you still are not sure which one applies, use [Choosing the Right Surface](https://docs.fastnear.com/agents/choosing-surfaces) first. + +## Account summary and holdings + +Use this when the user says things like "check this account", "what does this wallet hold", "what NFTs does this account have", or "which account does this key belong to?" + +**Minimum inputs** + +- network +- `account_id` or public key +- whether the user wants a broad summary or one specific asset class + +**Start here** + +- [V1 Full Account View](https://docs.fastnear.com/api/v1/account-full) for the broad account summary +- [V1 Public Key Lookup](https://docs.fastnear.com/api/v1/public-key) when the starting identifier is a public key +- [FastNear API index](https://docs.fastnear.com/api) when you need to choose a narrower endpoint first + +**Default sequence** + +1. If the starting identifier is a public key, resolve it to one or more account IDs with [V1 Public Key Lookup](https://docs.fastnear.com/api/v1/public-key). +2. Fetch the broadest useful account view with [V1 Full Account View](https://docs.fastnear.com/api/v1/account-full). +3. If the user asked for only one asset family or needs narrower detail, move to the targeted endpoints such as [FT balances](https://docs.fastnear.com/api/v1/account-ft), [NFT holdings](https://docs.fastnear.com/api/v1/account-nft), or [staking positions](https://docs.fastnear.com/api/v1/account-staking). +4. Stop once you can answer the holdings question directly. + +**Widen only if** + +- the user asks for exact canonical state fields rather than indexed summary data +- the user needs protocol-native account or access-key semantics + +When that happens, widen to [View Account](https://docs.fastnear.com/rpc/account/view-account) or other relevant pages in [RPC Reference](https://docs.fastnear.com/rpc). + +**A useful answer should contain** + +- the account identity you resolved +- the balances or holdings the user asked about +- a brief note if the answer is indexed summary data rather than raw RPC state + +## Transaction or receipt investigation + +Use this when the user says things like "did this transaction succeed", "why did it fail", "what happened to this receipt", or "show recent activity for this account." + +**Minimum inputs** + +- network +- transaction hash, receipt ID, or `account_id` +- whether the user wants one item inspected or a history range + +**Start here** + +- [Transactions by Hash](https://docs.fastnear.com/tx/transactions) for a transaction hash +- [Receipt Lookup](https://docs.fastnear.com/tx/receipt) for a receipt ID +- [Account History](https://docs.fastnear.com/tx/account) for account-centric activity + +**Default sequence** + +1. Choose the starting endpoint that matches the identifier you already have. +2. Fetch the indexed execution record and reconstruct the execution story in readable order. +3. Pull out the status, affected accounts, major receipts, and the block context if that is relevant. +4. Stop if you can explain what happened without needing canonical RPC confirmation. + +**Widen only if** + +- the user explicitly asks for exact RPC status semantics +- the indexed record is not enough to answer a protocol-level question +- the question shifts into transaction submission behavior + +When that happens, widen to [Transaction Status](https://docs.fastnear.com/rpc/transaction/tx-status) or another relevant method in [RPC Reference](https://docs.fastnear.com/rpc). + +**A useful answer should contain** + +- whether the transaction or receipt succeeded, failed, or is still pending +- the main execution takeaway first, before raw fields +- any follow-up path only if it adds value, such as "use RPC for canonical confirmation" + +## Transfer-only history + +Use this when the user cares about asset movement and does not need broader receipt or action context. + +**Minimum inputs** + +- network +- `account_id` +- optional filters such as token, direction, or time range + +**Start here** + +- [Query Transfers](https://docs.fastnear.com/transfers/query) +- [Transfers API index](https://docs.fastnear.com/transfers) + +**Default sequence** + +1. Query transfer history for the relevant account and filters. +2. Use pagination only as far as needed to answer the question. +3. Keep the answer focused on transfers rather than reconstructing the full transaction story. +4. Stop if the user only asked who sent what, when, and in what asset. + +**Widen only if** + +- the user starts asking about non-transfer actions +- the user needs receipt traces or broader execution context +- the user wants to explain why an action happened, not just that a transfer occurred + +When that happens, widen to [Account History](https://docs.fastnear.com/tx/account) or another relevant page in [Transactions API](https://docs.fastnear.com/tx). + +**A useful answer should contain** + +- the incoming or outgoing transfer events that matter +- any filter assumptions you made +- a note that this is transfer history, not full execution history + +## Recent block monitoring + +Use this when the user wants the latest optimistic or finalized block-family data, or asks "what changed recently?" + +**Minimum inputs** + +- network +- freshness requirement: optimistic or finalized +- optional block height or hash if the user is anchoring to a specific block + +**Start here** + +- [Last Final Block Redirect](https://docs.fastnear.com/neardata/last-block-final) for the latest finalized head +- [Optimistic Block by Height](https://docs.fastnear.com/neardata/block-optimistic) when the workflow is explicitly optimistic +- [Block Headers](https://docs.fastnear.com/neardata/block-headers) when header-level polling is enough +- [NEAR Data API index](https://docs.fastnear.com/neardata) when you need to choose among these + +**Default sequence** + +1. Decide whether the user needs optimistic freshness or finalized stability. +2. Use the latest-block helper or block-family route that matches that freshness requirement. +3. Poll explicitly and keep the answer clear about what freshness mode you used. +4. Stop if the user only needs recent block-family information and not canonical protocol follow-up. + +**Widen only if** + +- the user asks for exact canonical block output +- the user wants to inspect state or protocol fields beyond the block-family data +- the user needs exact RPC semantics for a specific block follow-up + +When that happens, widen to [RPC Reference](https://docs.fastnear.com/rpc), usually starting with [Block by Height](https://docs.fastnear.com/rpc/block/block-by-height) or [Block by Id](https://docs.fastnear.com/rpc/block/block-by-id). + +**A useful answer should contain** + +- whether the data came from optimistic or finalized reads +- the latest block or header details that actually answer the user's question +- a note when a deeper canonical follow-up would materially change interpretation + +## Contract storage inspection + +Use this when the user wants indexed contract key history, latest indexed key state, or contract-storage analysis by key. + +**Minimum inputs** + +- network +- contract ID +- exact key, key prefix, or account/predecessor scope +- whether the user wants latest indexed state or historical key changes + +**Start here** + +- [GET Latest by Exact Key](https://docs.fastnear.com/fastdata/kv/get-latest-key) for one exact key +- [KV FastData API index](https://docs.fastnear.com/fastdata/kv) when the question is broader than one key + +**Default sequence** + +1. Decide whether the user wants one key, a key family, or account-scoped storage history. +2. Fetch the smallest indexed key-value view that matches that scope. +3. If the user needs history rather than the latest value, stay inside [KV FastData API](https://docs.fastnear.com/fastdata/kv) and switch to the matching history endpoint. +4. Stop if indexed key-value data already answers the question. + +**Widen only if** + +- the user needs exact current on-chain state rather than indexed storage state +- the user needs protocol-native contract-state semantics +- the indexed storage view is insufficient for the exact key or prefix requested + +When that happens, widen to [View State](https://docs.fastnear.com/rpc/contract/view-state) in [RPC Reference](https://docs.fastnear.com/rpc). + +**A useful answer should contain** + +- the contract and key scope you inspected +- whether the result is latest indexed state or key history +- a note if canonical RPC state would differ in freshness or semantics + +## Node bootstrap and operator setup + +Use this when the user is trying to get infrastructure online rather than query chain data. + +**Minimum inputs** + +- network +- node type, such as RPC or archival +- whether the goal is bootstrap speed, sync recovery, or an operational runbook + +**Start here** + +- [Snapshots](https://docs.fastnear.com/snapshots) + +**Default sequence** + +1. Route immediately to the relevant snapshot or operator guide. +2. Keep the answer focused on prerequisites, bootstrap path, and operational next steps. +3. Do not pull application-level APIs unless the user later changes the task. + +**Widen only if** + +- the user stops asking about infrastructure and starts asking about chain data itself + +When that happens, return to [Choosing the Right Surface](https://docs.fastnear.com/agents/choosing-surfaces) and pick the correct data API from there. + +**A useful answer should contain** + +- the network and node type you are assuming +- the operator steps the user should follow next +- any clear prerequisite or caveat that changes the bootstrap path + +## Cross-playbook rules + +- State the network if you had to infer it. +- State the API you chose if the choice was an inference. +- Prefer one sufficient answer over an exhaustive multi-API answer. +- Treat pagination tokens as opaque and reuse them only with the endpoint and filter set that produced them. +- Do not widen just because a more canonical API exists. + +## If no playbook fits cleanly + +If the request is still ambiguous after reading this page: + +- use [Choosing the Right Surface](https://docs.fastnear.com/agents/choosing-surfaces) to pick the first API +- use [Auth for Agents](https://docs.fastnear.com/agents/auth) if the blocker is credential handling +- use [Building an MCP Server with FastNear](https://docs.fastnear.com/agents/mcp) if you are designing a reusable MCP tool surface rather than answering one user task +- return to [Agents on FastNear](https://docs.fastnear.com/agents) for the default workflow and answer-shape rules diff --git a/static/.well-known/agent-skills/surface-routing/SKILL.md b/static/.well-known/agent-skills/surface-routing/SKILL.md new file mode 100644 index 0000000..5cfb732 --- /dev/null +++ b/static/.well-known/agent-skills/surface-routing/SKILL.md @@ -0,0 +1,262 @@ +--- +name: "surface-routing" +description: "Surface-selection guide for FastNear. Use when you need to route a task to RPC, FastNear API, history APIs, NEAR Data, FastData, or snapshots." +--- +**Source:** [https://docs.fastnear.com/agents/choosing-surfaces](https://docs.fastnear.com/agents/choosing-surfaces) + +# Choosing the Right Surface + +Do not start by exposing every FastNear API to an agent. Start by translating the user's request into the job they actually want done, then pick the one FastNear API or reference section that most directly answers that job. + +For agents, the important question is usually not "which endpoint exists?" It is "what kind of answer will help the user next?" + +If your task is designing an MCP tool set rather than routing one user request, use [Building an MCP Server with FastNear](https://docs.fastnear.com/agents/mcp). + +## What decides the route + +Before you pick an API, identify four things: + +- **Object**: account, public key, transaction hash, receipt, block, contract storage, or infrastructure setup. +- **Answer shape**: product-style summary, execution history, canonical protocol output, or operator instructions. +- **Freshness**: historical, current, or latest/near-realtime. +- **Exactness**: indexed summary is acceptable, or canonical node-shaped correctness is required. + +In practice: + +- account plus summary usually means [FastNear API](https://docs.fastnear.com/api) +- account plus exact canonical state usually means [RPC Reference](https://docs.fastnear.com/rpc) +- transaction or receipt usually means [Transactions API](https://docs.fastnear.com/tx) +- transfer-only history usually means [Transfers API](https://docs.fastnear.com/transfers) +- newest blocks usually means [NEAR Data API](https://docs.fastnear.com/neardata) +- contract key history usually means [KV FastData API](https://docs.fastnear.com/fastdata/kv) +- node bootstrap usually means [Snapshots](https://docs.fastnear.com/snapshots) + +## Start from user intent + +- If the user wants a wallet-style or explorer-style answer, prefer indexed APIs. +- If the user wants canonical protocol behavior or exact node-shaped state, use raw [RPC Reference](https://docs.fastnear.com/rpc). +- If the user wants history, receipts, or event sequences, use history-oriented APIs before falling back to RPC. +- If the user wants the newest block-family data, use [NEAR Data API](https://docs.fastnear.com/neardata) for polling-oriented freshness. +- If the user wants infrastructure bootstrap instructions, route them to [Snapshots](https://docs.fastnear.com/snapshots) instead of application query surfaces. + +## Decision ladder + +Use this order of operations before you pick a surface: + +1. Is the user trying to stand up infrastructure rather than query chain data? + If yes, use [Snapshots](https://docs.fastnear.com/snapshots). +2. Is the user asking for a product-shaped summary such as balances, NFTs, staking, or account holdings? + If yes, start with [FastNear API](https://docs.fastnear.com/api). +3. Is the user asking what happened over time: transactions, receipts, transfers, or activity history? + If yes, start with [Transactions API](https://docs.fastnear.com/tx) or [Transfers API](https://docs.fastnear.com/transfers) for transfer-only questions. +4. Is the user focused on the newest blocks or low-latency block-family reads? + If yes, use [NEAR Data API](https://docs.fastnear.com/neardata). +5. Does correctness depend on canonical node behavior, protocol fields, or exact on-chain state? + If yes, use [RPC Reference](https://docs.fastnear.com/rpc). +6. Is the user asking about indexed key-value storage history or latest indexed contract state? + If yes, use [KV FastData API](https://docs.fastnear.com/fastdata/kv). + +If more than one surface could work, prefer the one that gives the most directly useful answer with the least reconstruction by the agent. + +## Before the first call + +Try to resolve these inputs before you make a request: + +- network: mainnet or testnet +- primary identifier: account ID, public key, transaction hash, receipt ID, block height/hash, contract ID plus storage key +- expected output: summary, history, canonical fields, or operator steps +- freshness requirement: latest, finalized, historical, or "whatever is current" + +If one of these is missing: + +- make a small assumption when the likely starting API does not change +- ask a clarifying question only when the wrong choice would materially change the result + +## Route common user asks + +| If the user says... | They probably want... | Start here | Only switch when... | +| --- | --- | --- | --- | +| "What is the exact on-chain account state?" | Canonical protocol-native state | [RPC Reference](https://docs.fastnear.com/rpc) | You also need a higher-level summary for humans. | +| "What does this account own?" | Product-shaped balances, NFTs, staking, and holdings | [FastNear API](https://docs.fastnear.com/api) | You need exact node fields the indexed view does not expose. | +| "What activity touched this account?" | Indexed transaction and receipt history | [Transactions API](https://docs.fastnear.com/tx) | The user only wants transfer events, or you need canonical protocol follow-up details. | +| "Show me transfers only." | Account-centric transfer history | [Transfers API](https://docs.fastnear.com/transfers) | The user actually needs broader transaction execution context. | +| "What changed in the latest blocks?" | Fresh optimistic or finalized block-family reads | [NEAR Data API](https://docs.fastnear.com/neardata) | You need canonical RPC details for a specific block or state read. | +| "What is the contract storage history here?" | Indexed key-value state history | [KV FastData API](https://docs.fastnear.com/fastdata/kv) | You need current canonical on-chain state rather than indexed history. | +| "Why did this transaction fail?" | An execution timeline first, then canonical details | [Transactions API](https://docs.fastnear.com/tx) | You need RPC-level confirmation of final protocol status or transaction submission behavior. | +| "How do I submit a transaction or inspect a protocol field?" | Canonical node behavior | [RPC Reference](https://docs.fastnear.com/rpc) | You later need to summarize the resulting account state or activity for a human. | +| "How do I bootstrap a node or archival setup?" | Infrastructure workflow, not app data | [Snapshots](https://docs.fastnear.com/snapshots) | The user then starts asking application-level chain questions. | + +## When the identifier is the clue + +If the user's wording is vague but the identifier is clear, let the identifier shape your first move: + +| If you have... | Default first move | Why | +| --- | --- | --- | +| an `account_id` | Start with [FastNear API](https://docs.fastnear.com/api) for summaries, or [RPC Reference](https://docs.fastnear.com/rpc) if the user explicitly asks for exact state | Account questions usually mean balances/holdings first unless the user says canonical. | +| a public key | Start with [FastNear API](https://docs.fastnear.com/api) for key-to-account resolution | This is usually an account discovery task, not an RPC-first task. | +| a transaction hash | Start with [Transactions API](https://docs.fastnear.com/tx) | Most users want execution context and readable history before raw protocol fields. | +| a receipt ID | Start with [Transactions API](https://docs.fastnear.com/tx) | Receipt tracing is already indexed there. | +| a block height or block hash | Start with [NEAR Data API](https://docs.fastnear.com/neardata) for freshness-oriented monitoring, or [RPC Reference](https://docs.fastnear.com/rpc) for exact canonical block data | The user's need is usually either recency or canonicality. | +| a contract ID plus storage key | Start with [KV FastData API](https://docs.fastnear.com/fastdata/kv) for indexed key history, or [RPC Reference](https://docs.fastnear.com/rpc) for exact current chain state | The storage question usually decides whether indexed history or canonical state matters. | +| a node or archival setup task | Start with [Snapshots](https://docs.fastnear.com/snapshots) | This is operator workflow, not application data access. | + +## What each surface is best at + +### RPC Reference + +Use [RPC Reference](https://docs.fastnear.com/rpc) when the user needs exact protocol-native data or behavior: + +- exact account state, access keys, validators, chunks, blocks, protocol metadata +- contract view calls and transaction submission +- answers where field names and semantics should stay close to NEAR nodes + +Do not lead with RPC when the user really wants a clean summary of holdings or history. That forces the agent to rebuild a product-shaped answer from lower-level data. + +### FastNear API + +Use [FastNear API](https://docs.fastnear.com/api) when the user wants an answer that already looks like application data: + +- balances +- NFTs +- staking positions +- public-key lookups +- combined account snapshots + +This should usually be the first stop for wallet, portfolio, explorer, and account overview requests. + +### Transactions API + +Use [Transactions API](https://docs.fastnear.com/tx) when the user wants execution history: + +- account activity +- transaction lookup +- receipt tracing +- block-scoped transaction history + +This is the default history surface when the user asks "what happened?" rather than "what exists right now?" + +### Transfers API + +Use [Transfers API](https://docs.fastnear.com/transfers) when the user's question is explicitly about transfer events and not full execution context: + +- incoming and outgoing transfers +- transfer-centric pagination flows +- transfer-only account activity views + +If the user starts asking about receipts, non-transfer actions, or full transaction behavior, move up to [Transactions API](https://docs.fastnear.com/tx). + +### NEAR Data API + +Use [NEAR Data API](https://docs.fastnear.com/neardata) when freshness matters more than a product-shaped summary: + +- optimistic or recently finalized blocks +- latest block-family reads +- explicit polling workflows + +Do not present this as a websocket or webhook product. It is a polling-oriented read surface. + +### KV FastData API + +Use [KV FastData API](https://docs.fastnear.com/fastdata/kv) when the question is about indexed contract storage history or latest indexed key-value state: + +- storage analysis +- key history +- contract state lookups where indexed key-value access is the right abstraction + +### Snapshots + +Use [Snapshots](https://docs.fastnear.com/snapshots) when the workflow is about operators standing up infrastructure: + +- mainnet or testnet bootstrap +- RPC or archival node initialization +- operator runbooks + +This is not an application query path. + +## Immediate next steps after choosing + +Once you choose a starting API, the next move should also be predictable: + +| Chosen API | First thing to do | What success looks like | Widen only if... | +| --- | --- | --- | --- | +| [FastNear API](https://docs.fastnear.com/api) | Pick the endpoint that matches the user's identifier or summary request | You can answer balances, holdings, staking, or account-summary questions directly | The user needs exact canonical node fields or protocol-native confirmation | +| [RPC Reference](https://docs.fastnear.com/rpc) | Choose the exact RPC method that matches the object and the required canonical field set | You can return protocol-native fields or perform the exact state/submit action requested | The user also needs a higher-level summary or indexed history | +| [Transactions API](https://docs.fastnear.com/tx) | Start from the transaction hash, receipt, account history, or block-history endpoint that matches the question | You can explain what happened and in what order | The user needs exact RPC-level finality or submission semantics | +| [Transfers API](https://docs.fastnear.com/transfers) | Fetch transfer history for the account or asset scope in question | You can answer transfer-only questions without unrelated execution detail | The user broadens the question to receipts, actions, or full transaction context | +| [NEAR Data API](https://docs.fastnear.com/neardata) | Fetch the latest optimistic or finalized block-family data that matches the freshness requirement | You can answer "what changed recently?" or "what is the latest block-family state?" | The user needs exact canonical block/state follow-up | +| [KV FastData API](https://docs.fastnear.com/fastdata/kv) | Fetch latest indexed key-value state or indexed key history | You can answer contract storage inspection questions in indexed form | The user needs exact on-chain current state instead of indexed storage views | +| [Snapshots](https://docs.fastnear.com/snapshots) | Choose the right network and node type, then follow the bootstrap guide | You can give operator steps, prerequisites, and bootstrap guidance | The user shifts from infra setup to application-level chain queries | + +## Stop conditions before you widen + +Do not widen to a second API just because it exists. Stay on the first API when: + +- the answer already matches the user's expected shape +- the current API already exposes the fields the user asked about +- the user asked for history and you already have indexed history +- the user asked for a summary and you already have a summary + +Widen when: + +- the user explicitly asks for canonical confirmation +- the current API lacks the field, freshness, or execution detail required +- the user broadens from transfer-only history to general transaction behavior +- the user broadens from summary output to protocol-native inspection + +## Combine surfaces only when it helps the user + +Good multi-surface patterns: + +- Start with [FastNear API](https://docs.fastnear.com/api), then drop to [RPC Reference](https://docs.fastnear.com/rpc) if the user asks for exact canonical confirmation. +- Start with [Transactions API](https://docs.fastnear.com/tx), then use [RPC Reference](https://docs.fastnear.com/rpc) if you need final protocol details for a specific transaction or receipt. +- Start with [NEAR Data API](https://docs.fastnear.com/neardata) for the newest blocks, then use [RPC Reference](https://docs.fastnear.com/rpc) for exact follow-up inspection of a specific block or state query. +- Start with [Transfers API](https://docs.fastnear.com/transfers) for transfer-only questions, then widen to [Transactions API](https://docs.fastnear.com/tx) if the user asks for more execution context. + +Bad multi-surface pattern: + +- Pull data from several surfaces before you know what the user actually wants. That usually produces a noisier answer, not a better one. + +## What the agent should infer from common phrasing + +- "What does this wallet have?" usually means balances, NFTs, staking, and maybe public-key resolution. Start with [FastNear API](https://docs.fastnear.com/api). +- "Why did this transaction fail?" usually means the user wants a readable execution story first, not raw protocol output. Start with [Transactions API](https://docs.fastnear.com/tx). +- "Is this the exact chain state?" usually means canonical correctness matters more than convenience. Start with [RPC Reference](https://docs.fastnear.com/rpc). +- "What just happened in the last block?" usually means freshness is the main requirement. Start with [NEAR Data API](https://docs.fastnear.com/neardata). +- "How do I get a node online quickly?" is an operator workflow. Start with [Snapshots](https://docs.fastnear.com/snapshots). + +## Common routing mistakes + +- Do not start with RPC just because it is canonical. Canonical is not the same as helpful for every user task. +- Do not use snapshots for application-level reads. +- Do not describe [NEAR Data API](https://docs.fastnear.com/neardata) as a streaming surface. +- Do not widen from transfer history to full transaction history unless the user's question actually broadens. +- Do not switch away from an indexed API just because raw RPC exists. Switch only when the indexed answer is insufficient. + +## If user intent is ambiguous + +When the user is vague, make the smallest useful routing assumption: + +- "Check this account" should usually begin with [FastNear API](https://docs.fastnear.com/api), because most users want a readable account summary. +- "Check this transaction" should usually begin with [Transactions API](https://docs.fastnear.com/tx), because most users want execution context, not only protocol fields. +- "Check this block" can start with [NEAR Data API](https://docs.fastnear.com/neardata) for recency-oriented monitoring or [RPC Reference](https://docs.fastnear.com/rpc) when the user explicitly cares about canonical node output. + +If you do make an assumption, state it briefly in the answer and move forward. Ask for clarification only when choosing the wrong surface would materially change the result. + +## What the agent should do after the first result + +After the first response comes back: + +1. Check whether you can now answer the user's question directly. +2. If yes, answer in the user's expected shape instead of collecting more data. +3. If no, name the missing piece precisely. + Examples: canonical confirmation, broader history, fresher block data, exact protocol field, or infra-specific context. +4. Only then switch APIs. + +The goal is not to prove that multiple FastNear APIs exist. The goal is to answer the user's next real question with the fewest necessary steps. + +## Related guides + +- [Agents on FastNear](https://docs.fastnear.com/agents) for the full surface map, base URLs, and prompt-ingestion hints. +- [Auth for Agents](https://docs.fastnear.com/agents/auth) for credential handling and runtime posture. +- [Agent Playbooks](https://docs.fastnear.com/agents/playbooks) for example multi-step workflows. +- [Building an MCP Server with FastNear](https://docs.fastnear.com/agents/mcp) for a recommended first tool set and a direct-HTTP TypeScript example. diff --git a/static/.well-known/api-catalog b/static/.well-known/api-catalog new file mode 100644 index 0000000..12425cd --- /dev/null +++ b/static/.well-known/api-catalog @@ -0,0 +1,67 @@ +{ + "linkset": [ + { + "anchor": "https://rpc.mainnet.fastnear.com", + "service-desc": [ + { + "href": "https://rpc.mainnet.fastnear.com/openapi.json", + "type": "application/json" + } + ], + "service-doc": [ + { + "href": "https://docs.fastnear.com/rpc", + "type": "text/html" + } + ], + "status": [ + { + "href": "https://status.fastnear.com/status/main", + "type": "text/html" + } + ] + }, + { + "anchor": "https://api.fastnear.com", + "service-desc": [ + { + "href": "https://docs.fastnear.com/openapi/fastnear.json", + "type": "application/json" + } + ], + "service-doc": [ + { + "href": "https://docs.fastnear.com/api", + "type": "text/html" + } + ], + "status": [ + { + "href": "https://api.fastnear.com/status", + "type": "application/json" + } + ] + }, + { + "anchor": "https://mainnet.neardata.xyz", + "service-desc": [ + { + "href": "https://docs.fastnear.com/openapi/neardata.json", + "type": "application/json" + } + ], + "service-doc": [ + { + "href": "https://docs.fastnear.com/neardata", + "type": "text/html" + } + ], + "status": [ + { + "href": "https://mainnet.neardata.xyz/health", + "type": "application/json" + } + ] + } + ] +} diff --git a/static/_headers b/static/_headers new file mode 100644 index 0000000..d1f733a --- /dev/null +++ b/static/_headers @@ -0,0 +1,21 @@ +/ + Link: ; rel="api-catalog"; type="application/linkset+json" + Link: ; rel="service-doc"; type="text/html" + Link: ; rel="service-meta"; type="application/json" + Link: ; rel="service-meta"; type="application/json" + +/ru + Link: ; rel="api-catalog"; type="application/linkset+json" + Link: ; rel="service-doc"; type="text/html" + Link: ; rel="service-meta"; type="application/json" + Link: ; rel="service-meta"; type="application/json" + +/.well-known/api-catalog + Content-Type: application/linkset+json; charset=utf-8; profile="https://www.rfc-editor.org/info/rfc9727" + Link: ; rel="api-catalog"; type="application/linkset+json" + +/.well-known/agent-skills/index.json + Content-Type: application/json; charset=utf-8 + +/.well-known/agent-skills/*/SKILL.md + Content-Type: text/markdown; charset=utf-8 diff --git a/static/_worker.js b/static/_worker.js new file mode 100644 index 0000000..17caae4 --- /dev/null +++ b/static/_worker.js @@ -0,0 +1,121 @@ +const MARKDOWN_CONTENT_TYPE = "text/markdown; charset=utf-8"; +const MARKDOWN_ACCEPT_TOKEN = "text/markdown"; +const LOCALE_ROOTS = new Set(["/ru"]); +const DISCOVERY_LINKS_BY_PATH = { + "/": [ + '; rel="api-catalog"; type="application/linkset+json"', + '; rel="service-doc"; type="text/html"', + '; rel="service-meta"; type="application/json"', + '; rel="service-meta"; type="application/json"', + ], + "/ru": [ + '; rel="api-catalog"; type="application/linkset+json"', + '; rel="service-doc"; type="text/html"', + '; rel="service-meta"; type="application/json"', + '; rel="service-meta"; type="application/json"', + ], + "/.well-known/api-catalog": [ + '; rel="api-catalog"; type="application/linkset+json"', + ], +}; + +function wantsMarkdown(request) { + const accept = request.headers.get("Accept") || ""; + return accept.toLowerCase().includes(MARKDOWN_ACCEPT_TOKEN); +} + +function appendVaryAccept(headers) { + const vary = headers.get("Vary"); + if (!vary) { + headers.set("Vary", "Accept"); + return; + } + + const parts = vary + .split(",") + .map((value) => value.trim()) + .filter(Boolean); + if (!parts.some((value) => value.toLowerCase() === "accept")) { + parts.push("Accept"); + headers.set("Vary", parts.join(", ")); + } +} + +function appendDiscoveryLinks(headers, pathname) { + const normalizedPathname = + pathname !== "/" && pathname.endsWith("/") ? pathname.replace(/\/+$/, "") : pathname; + const links = DISCOVERY_LINKS_BY_PATH[normalizedPathname]; + if (!links) { + return; + } + + for (const link of links) { + headers.append("Link", link); + } +} + +function resolveMarkdownPathname(pathname) { + if (!pathname || pathname.includes(".")) { + return null; + } + + if (pathname === "/") { + return "/index.md"; + } + + if (LOCALE_ROOTS.has(pathname)) { + return `${pathname}/index.md`; + } + + if (pathname.endsWith("/")) { + return `${pathname}index.md`; + } + + return `${pathname}.md`; +} + +async function fetchAsset(request, env, pathname) { + const url = new URL(request.url); + url.pathname = pathname; + return env.ASSETS.fetch(new Request(url.toString(), request)); +} + +function cloneResponse(response, headers) { + return new Response(response.body, { + headers, + status: response.status, + statusText: response.statusText, + }); +} + +export default { + async fetch(request, env) { + const url = new URL(request.url); + const markdownPathname = resolveMarkdownPathname(url.pathname); + + if (!markdownPathname) { + return env.ASSETS.fetch(request); + } + + if (!wantsMarkdown(request)) { + const response = await env.ASSETS.fetch(request); + const headers = new Headers(response.headers); + appendVaryAccept(headers); + return cloneResponse(response, headers); + } + + const markdownResponse = await fetchAsset(request, env, markdownPathname); + if (markdownResponse.status >= 400) { + const fallback = await env.ASSETS.fetch(request); + const headers = new Headers(fallback.headers); + appendVaryAccept(headers); + return cloneResponse(fallback, headers); + } + + const headers = new Headers(markdownResponse.headers); + headers.set("Content-Type", MARKDOWN_CONTENT_TYPE); + appendVaryAccept(headers); + appendDiscoveryLinks(headers, url.pathname); + return cloneResponse(markdownResponse, headers); + }, +}; diff --git a/static/openapi/fastnear.json b/static/openapi/fastnear.json new file mode 100644 index 0000000..8c85383 --- /dev/null +++ b/static/openapi/fastnear.json @@ -0,0 +1,1324 @@ +{ + "components": { + "schemas": { + "AccountBalanceRow": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "balance": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "account_id", + "balance" + ], + "type": "object" + }, + "AccountFullResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "nfts": { + "items": { + "$ref": "#/components/schemas/NftRow" + }, + "type": "array" + }, + "pools": { + "items": { + "$ref": "#/components/schemas/PoolRow" + }, + "type": "array" + }, + "state": { + "allOf": [ + { + "$ref": "#/components/schemas/AccountStateResponse" + } + ], + "nullable": true, + "type": "object" + }, + "tokens": { + "items": { + "$ref": "#/components/schemas/TokenRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "pools", + "tokens", + "nfts", + "state" + ], + "type": "object" + }, + "AccountStateResponse": { + "additionalProperties": false, + "properties": { + "balance": { + "nullable": true, + "type": "string" + }, + "locked": { + "nullable": true, + "type": "string" + }, + "storage_bytes": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "required": [ + "balance", + "locked", + "storage_bytes" + ], + "type": "object" + }, + "HealthResponse": { + "additionalProperties": false, + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "NftRow": { + "additionalProperties": false, + "properties": { + "contract_id": { + "type": "string" + }, + "last_update_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "required": [ + "contract_id", + "last_update_block_height" + ], + "type": "object" + }, + "PoolRow": { + "additionalProperties": false, + "properties": { + "last_update_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "pool_id": { + "type": "string" + } + }, + "required": [ + "pool_id", + "last_update_block_height" + ], + "type": "object" + }, + "PublicKeyLookupResponse": { + "additionalProperties": false, + "properties": { + "account_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "public_key": { + "type": "string" + } + }, + "required": [ + "public_key", + "account_ids" + ], + "type": "object" + }, + "StatusResponse": { + "additionalProperties": false, + "properties": { + "sync_balance_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "sync_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "sync_block_timestamp_nanosec": { + "nullable": true, + "type": "string" + }, + "sync_latency_sec": { + "format": "double", + "nullable": true, + "type": "number" + }, + "version": { + "type": "string" + } + }, + "required": [ + "version", + "sync_block_height", + "sync_latency_sec", + "sync_block_timestamp_nanosec", + "sync_balance_block_height" + ], + "type": "object" + }, + "TokenAccountsResponse": { + "additionalProperties": false, + "properties": { + "accounts": { + "items": { + "$ref": "#/components/schemas/AccountBalanceRow" + }, + "type": "array" + }, + "token_id": { + "type": "string" + } + }, + "required": [ + "token_id", + "accounts" + ], + "type": "object" + }, + "TokenRow": { + "additionalProperties": false, + "properties": { + "balance": { + "nullable": true, + "type": "string" + }, + "contract_id": { + "type": "string" + }, + "last_update_block_height": { + "format": "uint64", + "minimum": 0, + "nullable": true, + "type": "integer" + } + }, + "required": [ + "contract_id", + "last_update_block_height", + "balance" + ], + "type": "object" + }, + "V0ContractsResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "contract_ids": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "contract_ids" + ], + "type": "object" + }, + "V0StakingResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "pools": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "pools" + ], + "type": "object" + }, + "V1FtResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "tokens": { + "items": { + "$ref": "#/components/schemas/TokenRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "tokens" + ], + "type": "object" + }, + "V1NftResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "tokens": { + "items": { + "$ref": "#/components/schemas/NftRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "tokens" + ], + "type": "object" + }, + "V1StakingResponse": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "pools": { + "items": { + "$ref": "#/components/schemas/PoolRow" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "pools" + ], + "type": "object" + } + } + }, + "info": { + "description": "Low-latency indexed account, token, and public-key lookup APIs for wallets and explorers. Embedded portal clients may forward an optional `apiKey` query parameter, but the public FastNEAR API does not require it.", + "title": "FastNEAR API", + "version": "3.0.3" + }, + "openapi": "3.0.3", + "paths": { + "/health": { + "get": { + "description": "Ping the FastNEAR API for liveness — returns `{status: ok}` when healthy.", + "operationId": "get_health", + "parameters": [ + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "status": "ok" + }, + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + }, + "description": "Health status string" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Get service health", + "tags": [ + "system" + ], + "x-fastnear-slug": "health", + "x-fastnear-title": "FastNEAR API - Health" + } + }, + "/status": { + "get": { + "description": "Check the current indexed block height, latency, and deployed service version.", + "operationId": "get_status", + "parameters": [ + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "sync_balance_block_height": 129734103, + "sync_block_height": 129734103, + "sync_block_timestamp_nanosec": "1728256282197171397", + "sync_latency_sec": 4.671730603, + "version": "0.10.0" + }, + "schema": { + "$ref": "#/components/schemas/StatusResponse" + } + } + }, + "description": "Current FastNEAR API sync status" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Get service sync status", + "tags": [ + "system" + ], + "x-fastnear-slug": "status", + "x-fastnear-title": "FastNEAR API - Status" + } + }, + "/v0/account/{account_id}/ft": { + "get": { + "description": "Fetch the fungible token contract IDs an account has held — contract IDs only, no balances.", + "operationId": "account_ft_v0", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "here.tg", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "here.tg", + "contract_ids": [ + "wrap.near", + "usdt.tether-token.near" + ] + }, + "schema": { + "$ref": "#/components/schemas/V0ContractsResponse" + } + } + }, + "description": "Fungible token contract IDs for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup fungible token contract IDs for an account", + "tags": [ + "fungible-tokens" + ], + "x-fastnear-slug": "account_ft", + "x-fastnear-title": "FastNEAR API - V0 Account FT" + } + }, + "/v0/account/{account_id}/nft": { + "get": { + "description": "Fetch the NFT contract IDs an account has held — contract IDs only, no block-height metadata.", + "operationId": "account_nft_v0", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "sharddog.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "sharddog.near", + "contract_ids": [ + "nft.example.near" + ] + }, + "schema": { + "$ref": "#/components/schemas/V0ContractsResponse" + } + } + }, + "description": "NFT contract IDs for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup NFT contract IDs for an account", + "tags": [ + "non-fungible-tokens" + ], + "x-fastnear-slug": "account_nft", + "x-fastnear-title": "FastNEAR API - V0 Account NFT" + } + }, + "/v0/account/{account_id}/staking": { + "get": { + "description": "Fetch staking pool account IDs for one account — pool IDs only, no block-height metadata.", + "operationId": "account_staking_v0", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "mob.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "mob.near", + "pools": [ + "zavodil.poolv1.near" + ] + }, + "schema": { + "$ref": "#/components/schemas/V0StakingResponse" + } + } + }, + "description": "Staking pool account IDs for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup staking pool account IDs for an account", + "tags": [ + "staking" + ], + "x-fastnear-slug": "account_staking", + "x-fastnear-title": "FastNEAR API - V0 Account Staking" + } + }, + "/v0/public_key/{public_key}": { + "get": { + "description": "Fetch the account IDs that have registered a full-access public key, via the legacy V0 lookup path.", + "operationId": "lookup_by_public_key_v0", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied full-access public key" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup full-access accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup", + "x-fastnear-title": "FastNEAR API - V0 Public Key Lookup" + } + }, + "/v0/public_key/{public_key}/all": { + "get": { + "description": "List every account tied to a public key — full-access and limited-access keys together — via the legacy V0 lookup-all path.", + "operationId": "lookup_by_public_key_all_v0", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied public key, including limited-access keys" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup all indexed accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup_all", + "x-fastnear-title": "FastNEAR API - V0 Public Key Lookup (All)" + } + }, + "/v1/account/{account_id}/ft": { + "get": { + "description": "Fetch an account's fungible token balance rows, each with contract ID, balance, and last-update block height.", + "operationId": "account_ft_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "here.tg", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "here.tg", + "tokens": [ + { + "balance": "1000000000000000000000000", + "contract_id": "wrap.near", + "last_update_block_height": null + } + ] + }, + "schema": { + "$ref": "#/components/schemas/V1FtResponse" + } + } + }, + "description": "Indexed fungible token rows for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup indexed fungible token rows for an account", + "tags": [ + "fungible-tokens" + ], + "x-fastnear-slug": "account_ft", + "x-fastnear-title": "FastNEAR API - V1 Account FT" + } + }, + "/v1/account/{account_id}/full": { + "get": { + "description": "Fetch the combined indexed account view, including staking pools, FT balances, NFTs, and account state.", + "operationId": "account_full_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "here.tg", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "here.tg", + "nfts": [], + "pools": [], + "state": { + "balance": "1000000000000000000000000", + "locked": "0", + "storage_bytes": 512 + }, + "tokens": [] + }, + "schema": { + "$ref": "#/components/schemas/AccountFullResponse" + } + } + }, + "description": "Full indexed account information for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup full indexed account information", + "tags": [ + "accounts" + ], + "x-fastnear-slug": "account_full", + "x-fastnear-title": "FastNEAR API - V1 Account Full" + } + }, + "/v1/account/{account_id}/nft": { + "get": { + "description": "Fetch NFT contract rows for an account, including block-height metadata for each contract.", + "operationId": "account_nft_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "sharddog.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "sharddog.near", + "tokens": [ + { + "contract_id": "nft.example.near", + "last_update_block_height": null + } + ] + }, + "schema": { + "$ref": "#/components/schemas/V1NftResponse" + } + } + }, + "description": "Indexed NFT contract rows for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup indexed NFT contract rows for an account", + "tags": [ + "non-fungible-tokens" + ], + "x-fastnear-slug": "account_nft", + "x-fastnear-title": "FastNEAR API - V1 Account NFT" + } + }, + "/v1/account/{account_id}/staking": { + "get": { + "description": "Retrieve staking pool rows for an account, including block-height metadata for each pool relationship.", + "operationId": "account_staking_v1", + "parameters": [ + { + "description": "NEAR account ID to inspect.", + "example": "mob.near", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_id": "mob.near", + "pools": [ + { + "last_update_block_height": null, + "pool_id": "zavodil.poolv1.near" + } + ] + }, + "schema": { + "$ref": "#/components/schemas/V1StakingResponse" + } + } + }, + "description": "Indexed staking pool rows for the requested account" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup indexed staking pools for an account", + "tags": [ + "staking" + ], + "x-fastnear-slug": "account_staking", + "x-fastnear-title": "FastNEAR API - V1 Account Staking" + } + }, + "/v1/ft/{token_id}/top": { + "get": { + "description": "Fetch the top-balance holder list for a fungible token contract, ranked highest balance first.", + "operationId": "ft_top_v1", + "parameters": [ + { + "description": "Fungible token contract account ID.", + "example": "wrap.near", + "in": "path", + "name": "token_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "accounts": [ + { + "account_id": "mob.near", + "balance": "979894691374420631019486155" + } + ], + "token_id": "wrap.near" + }, + "schema": { + "$ref": "#/components/schemas/TokenAccountsResponse" + } + } + }, + "description": "Indexed top holders for the requested fungible token" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup top indexed holders for a fungible token", + "tags": [ + "fungible-tokens" + ], + "x-fastnear-slug": "ft_top", + "x-fastnear-title": "FastNEAR API - V1 FT Top Holders" + } + }, + "/v1/public_key/{public_key}": { + "get": { + "description": "Resolve a full-access public key to the indexed NEAR accounts that have registered it — V1 canonical lookup path.", + "operationId": "lookup_by_public_key_v1", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied full-access public key" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup full-access accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup", + "x-fastnear-title": "FastNEAR API - V1 Public Key Lookup" + } + }, + "/v1/public_key/{public_key}/all": { + "get": { + "description": "Resolve a public key to every account that holds it — full-access and limited-access keys alike — via the V1 lookup-all path.", + "operationId": "lookup_by_public_key_all_v1", + "parameters": [ + { + "description": "NEAR public key in `ed25519:...` or `secp256k1:...` form.", + "example": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT", + "in": "path", + "name": "public_key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional API key forwarded by embedded portal clients. The public FastNEAR API does not require it.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_ids": [ + "root.near" + ], + "public_key": "ed25519:CCaThr3uokqnUs6Z5vVnaDcJdrfuTpYJHJWcAGubDjT" + }, + "schema": { + "$ref": "#/components/schemas/PublicKeyLookupResponse" + } + } + }, + "description": "Matching account IDs for the supplied public key, including limited-access keys" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Internal Server Error" + } + }, + "summary": "Lookup all indexed accounts by public key", + "tags": [ + "public-key" + ], + "x-fastnear-slug": "public_key_lookup_all", + "x-fastnear-title": "FastNEAR API - V1 Public Key Lookup (All)" + } + } + }, + "servers": [ + { + "description": "Mainnet", + "url": "https://api.fastnear.com" + }, + { + "description": "Testnet", + "url": "https://test.api.fastnear.com" + } + ] +} diff --git a/static/openapi/neardata.json b/static/openapi/neardata.json new file mode 100644 index 0000000..46f72f0 --- /dev/null +++ b/static/openapi/neardata.json @@ -0,0 +1,1536 @@ +{ + "components": { + "schemas": { + "ActionDocument": { + "additionalProperties": true, + "type": "object" + }, + "ActionReceiptBody": { + "additionalProperties": false, + "properties": { + "Action": { + "$ref": "#/components/schemas/ActionReceiptDocument" + } + }, + "required": [ + "Action" + ], + "type": "object" + }, + "ActionReceiptDocument": { + "additionalProperties": false, + "properties": { + "actions": { + "items": { + "$ref": "#/components/schemas/ActionDocument" + }, + "type": "array" + }, + "gas_price": { + "type": "string" + }, + "input_data_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "is_promise_yield": { + "type": "boolean" + }, + "output_data_receivers": { + "items": { + "$ref": "#/components/schemas/OutputDataReceiverDocument" + }, + "type": "array" + }, + "signer_id": { + "type": "string" + }, + "signer_public_key": { + "type": "string" + } + }, + "required": [ + "actions", + "gas_price", + "input_data_ids", + "is_promise_yield", + "output_data_receivers", + "signer_id", + "signer_public_key" + ], + "type": "object" + }, + "BlockDocument": { + "additionalProperties": false, + "description": "Full block document as served by neardata, including the block envelope and per-shard payloads.", + "properties": { + "block": { + "$ref": "#/components/schemas/BlockEnvelope" + }, + "shards": { + "items": { + "$ref": "#/components/schemas/ShardDocument" + }, + "type": "array" + } + }, + "required": [ + "block", + "shards" + ], + "type": "object" + }, + "BlockEnvelope": { + "additionalProperties": false, + "description": "Block-level payload returned by neardata.", + "properties": { + "author": { + "description": "Block producer account ID.", + "type": "string" + }, + "chunks": { + "items": { + "$ref": "#/components/schemas/ChunkHeader" + }, + "type": "array" + }, + "header": { + "$ref": "#/components/schemas/BlockHeader" + } + }, + "required": [ + "author", + "chunks", + "header" + ], + "type": "object" + }, + "BlockErrorResponse": { + "additionalProperties": false, + "properties": { + "error": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/BlockErrorType" + } + }, + "required": [ + "error", + "type" + ], + "type": "object" + }, + "BlockErrorType": { + "enum": [ + "BLOCK_HEIGHT_TOO_HIGH", + "BLOCK_HEIGHT_TOO_LOW", + "BLOCK_DOES_NOT_EXIST" + ], + "type": "string" + }, + "BlockHeader": { + "additionalProperties": true, + "description": "Block header object as served by neardata.", + "properties": { + "chunks_included": { + "format": "uint64", + "type": "integer" + }, + "epoch_id": { + "type": "string" + }, + "gas_price": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "height": { + "format": "uint64", + "type": "integer" + }, + "next_epoch_id": { + "type": "string" + }, + "prev_hash": { + "type": "string" + }, + "prev_height": { + "format": "uint64", + "type": "integer" + }, + "timestamp": { + "type": "integer" + }, + "timestamp_nanosec": { + "type": "string" + }, + "total_supply": { + "type": "string" + } + }, + "type": "object" + }, + "ChunkDocument": { + "additionalProperties": false, + "description": "Chunk payload returned by neardata for a single shard in a selected block.", + "properties": { + "author": { + "description": "Chunk producer account ID.", + "type": "string" + }, + "header": { + "$ref": "#/components/schemas/ChunkHeader" + }, + "receipts": { + "items": { + "$ref": "#/components/schemas/ReceiptDocument" + }, + "type": "array" + }, + "transactions": { + "items": { + "$ref": "#/components/schemas/ChunkTransactionWrapper" + }, + "type": "array" + } + }, + "required": [ + "author", + "header", + "receipts", + "transactions" + ], + "type": "object" + }, + "ChunkHeader": { + "additionalProperties": true, + "description": "Chunk header object as served by neardata.", + "properties": { + "chunk_hash": { + "type": "string" + }, + "gas_limit": { + "type": "integer" + }, + "gas_used": { + "type": "integer" + }, + "height_created": { + "format": "uint64", + "type": "integer" + }, + "height_included": { + "format": "uint64", + "type": "integer" + }, + "outcome_root": { + "type": "string" + }, + "outgoing_receipts_root": { + "type": "string" + }, + "prev_block_hash": { + "type": "string" + }, + "shard_id": { + "format": "uint64", + "type": "integer" + }, + "tx_root": { + "type": "string" + } + }, + "type": "object" + }, + "ChunkTransactionWrapper": { + "additionalProperties": false, + "description": "Transaction entry returned inside a neardata chunk.", + "properties": { + "outcome": { + "$ref": "#/components/schemas/ExecutionWithReceipt" + }, + "transaction": { + "$ref": "#/components/schemas/SignedTransactionDocument" + } + }, + "required": [ + "outcome", + "transaction" + ], + "type": "object" + }, + "DataReceiptBody": { + "additionalProperties": false, + "properties": { + "Data": { + "$ref": "#/components/schemas/DataReceiptDocument" + } + }, + "required": [ + "Data" + ], + "type": "object" + }, + "DataReceiptDocument": { + "additionalProperties": false, + "properties": { + "data": { + "type": "string" + }, + "data_id": { + "type": "string" + }, + "is_promise_resume": { + "type": "boolean" + } + }, + "required": [ + "data", + "data_id", + "is_promise_resume" + ], + "type": "object" + }, + "ExecutionOutcomeDocument": { + "additionalProperties": false, + "properties": { + "block_hash": { + "type": "string" + }, + "id": { + "type": "string" + }, + "outcome": { + "$ref": "#/components/schemas/ExecutionOutcomeSummary" + }, + "proof": { + "items": { + "$ref": "#/components/schemas/ExecutionProofItem" + }, + "type": "array" + } + }, + "required": [ + "block_hash", + "id", + "outcome", + "proof" + ], + "type": "object" + }, + "ExecutionOutcomeStatus": { + "oneOf": [ + { + "$ref": "#/components/schemas/ExecutionOutcomeStatusSuccessReceiptId" + }, + { + "$ref": "#/components/schemas/ExecutionOutcomeStatusSuccessValue" + }, + { + "$ref": "#/components/schemas/ExecutionOutcomeStatusFailure" + } + ] + }, + "ExecutionOutcomeStatusFailure": { + "additionalProperties": false, + "properties": { + "Failure": { + "additionalProperties": true, + "type": "object" + } + }, + "required": [ + "Failure" + ], + "type": "object" + }, + "ExecutionOutcomeStatusSuccessReceiptId": { + "additionalProperties": false, + "properties": { + "SuccessReceiptId": { + "type": "string" + } + }, + "required": [ + "SuccessReceiptId" + ], + "type": "object" + }, + "ExecutionOutcomeStatusSuccessValue": { + "additionalProperties": false, + "properties": { + "SuccessValue": { + "type": "string" + } + }, + "required": [ + "SuccessValue" + ], + "type": "object" + }, + "ExecutionOutcomeSummary": { + "additionalProperties": false, + "properties": { + "executor_id": { + "type": "string" + }, + "gas_burnt": { + "format": "uint64", + "type": "integer" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "type": "object" + }, + "receipt_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "$ref": "#/components/schemas/ExecutionOutcomeStatus" + }, + "tokens_burnt": { + "type": "string" + } + }, + "required": [ + "executor_id", + "gas_burnt", + "logs", + "metadata", + "receipt_ids", + "status", + "tokens_burnt" + ], + "type": "object" + }, + "ExecutionProofItem": { + "additionalProperties": true, + "type": "object" + }, + "ExecutionWithReceipt": { + "additionalProperties": false, + "description": "Execution result paired with an optional receipt object.", + "properties": { + "execution_outcome": { + "$ref": "#/components/schemas/ExecutionOutcomeDocument" + }, + "receipt": { + "description": "Receipt payload when neardata includes it for this entry.", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/ReceiptDocument" + }, + { + "$ref": "#/components/schemas/OmittedReceiptDocument" + } + ], + "type": "object" + }, + "tx_hash": { + "type": "string" + } + }, + "required": [ + "execution_outcome", + "receipt" + ], + "type": "object" + }, + "HealthResponse": { + "additionalProperties": false, + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "OmittedReceiptDocument": { + "additionalProperties": false, + "type": "object" + }, + "OutputDataReceiverDocument": { + "additionalProperties": false, + "properties": { + "data_id": { + "type": "string" + }, + "receiver_id": { + "type": "string" + } + }, + "required": [ + "data_id", + "receiver_id" + ], + "type": "object" + }, + "ReceiptBody": { + "oneOf": [ + { + "$ref": "#/components/schemas/ActionReceiptBody" + }, + { + "$ref": "#/components/schemas/DataReceiptBody" + } + ] + }, + "ReceiptDocument": { + "additionalProperties": false, + "description": "Receipt object as served by neardata inside a chunk payload.", + "properties": { + "predecessor_id": { + "type": "string" + }, + "priority": { + "format": "uint64", + "type": "integer" + }, + "receipt": { + "$ref": "#/components/schemas/ReceiptBody" + }, + "receipt_id": { + "type": "string" + }, + "receiver_id": { + "type": "string" + } + }, + "required": [ + "predecessor_id", + "priority", + "receipt", + "receipt_id", + "receiver_id" + ], + "type": "object" + }, + "ShardDocument": { + "additionalProperties": false, + "description": "Per-shard payload returned by neardata for a block.", + "properties": { + "chunk": { + "$ref": "#/components/schemas/ChunkDocument" + }, + "receipt_execution_outcomes": { + "items": { + "$ref": "#/components/schemas/ExecutionWithReceipt" + }, + "type": "array" + }, + "shard_id": { + "format": "uint64", + "type": "integer" + }, + "state_changes": { + "items": { + "$ref": "#/components/schemas/StateChangeItem" + }, + "type": "array" + } + }, + "required": [ + "chunk", + "receipt_execution_outcomes", + "shard_id", + "state_changes" + ], + "type": "object" + }, + "SignedTransactionDocument": { + "additionalProperties": false, + "properties": { + "actions": { + "items": { + "$ref": "#/components/schemas/ActionDocument" + }, + "type": "array" + }, + "hash": { + "type": "string" + }, + "nonce": { + "format": "uint64", + "type": "integer" + }, + "priority_fee": { + "format": "uint64", + "type": "integer" + }, + "public_key": { + "type": "string" + }, + "receiver_id": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "signer_id": { + "type": "string" + } + }, + "required": [ + "actions", + "hash", + "nonce", + "priority_fee", + "public_key", + "receiver_id", + "signature", + "signer_id" + ], + "type": "object" + }, + "StateChangeCause": { + "oneOf": [ + { + "$ref": "#/components/schemas/StateChangeCauseTransactionProcessing" + }, + { + "$ref": "#/components/schemas/StateChangeCauseReceiptProcessing" + }, + { + "$ref": "#/components/schemas/StateChangeCauseActionReceiptGasReward" + } + ] + }, + "StateChangeCauseActionReceiptGasReward": { + "additionalProperties": false, + "properties": { + "receipt_hash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "receipt_hash", + "type" + ], + "type": "object" + }, + "StateChangeCauseReceiptProcessing": { + "additionalProperties": false, + "properties": { + "receipt_hash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "receipt_hash", + "type" + ], + "type": "object" + }, + "StateChangeCauseTransactionProcessing": { + "additionalProperties": false, + "properties": { + "tx_hash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "tx_hash", + "type" + ], + "type": "object" + }, + "StateChangeItem": { + "additionalProperties": false, + "description": "State change entry returned by neardata for a shard.", + "properties": { + "cause": { + "$ref": "#/components/schemas/StateChangeCause" + }, + "change": { + "$ref": "#/components/schemas/StateChangeValue" + }, + "type": { + "type": "string" + } + }, + "required": [ + "cause", + "change", + "type" + ], + "type": "object" + }, + "StateChangeValue": { + "oneOf": [ + { + "$ref": "#/components/schemas/StateChangeValueAccountUpdate" + }, + { + "$ref": "#/components/schemas/StateChangeValueAccessKeyUpdate" + }, + { + "$ref": "#/components/schemas/StateChangeValueDataUpdate" + }, + { + "$ref": "#/components/schemas/StateChangeValueDataDeletion" + } + ] + }, + "StateChangeValueAccessKeyUpdate": { + "additionalProperties": false, + "properties": { + "access_key": { + "additionalProperties": true, + "type": "object" + }, + "account_id": { + "type": "string" + }, + "public_key": { + "type": "string" + } + }, + "required": [ + "access_key", + "account_id", + "public_key" + ], + "type": "object" + }, + "StateChangeValueAccountUpdate": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "amount": { + "type": "string" + }, + "code_hash": { + "type": "string" + }, + "locked": { + "type": "string" + }, + "storage_paid_at": { + "format": "uint64", + "type": "integer" + }, + "storage_usage": { + "format": "uint64", + "type": "integer" + } + }, + "required": [ + "account_id", + "amount", + "code_hash", + "locked", + "storage_paid_at", + "storage_usage" + ], + "type": "object" + }, + "StateChangeValueDataDeletion": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "key_base64": { + "type": "string" + } + }, + "required": [ + "account_id", + "key_base64" + ], + "type": "object" + }, + "StateChangeValueDataUpdate": { + "additionalProperties": false, + "properties": { + "account_id": { + "type": "string" + }, + "key_base64": { + "type": "string" + }, + "value_base64": { + "type": "string" + } + }, + "required": [ + "account_id", + "key_base64", + "value_base64" + ], + "type": "object" + } + } + }, + "info": { + "description": "Cached and archived NEAR block data with redirect helpers for first-block and latest-block workflows. Some block-family routes may redirect depending on archive or freshness topology.", + "title": "NEAR Data API", + "version": "3.0.3" + }, + "openapi": "3.0.3", + "paths": { + "/health": { + "get": { + "description": "Ping the neardata service for liveness — returns `{status: ok}` when healthy, errors otherwise.", + "operationId": "get_health", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "status": "ok" + }, + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + }, + "description": "Health payload" + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Get service health", + "tags": [ + "system" + ], + "x-fastnear-slug": "health", + "x-fastnear-title": "NEAR Data API - Health" + } + }, + "/v0/block/{block_height}": { + "get": { + "description": "Fetch a finalized block's full document at a chosen height — header plus every chunk and shard payload.", + "operationId": "get_block", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch a finalized block by height", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block", + "x-fastnear-title": "NEAR Data API - Block" + } + }, + "/v0/block/{block_height}/chunk/{shard_id}": { + "get": { + "description": "Fetch one chunk — a single shard's transactions and incoming receipts — at a chosen block height.", + "operationId": "get_chunk", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Shard ID whose chunk should be returned.", + "example": "0", + "in": "path", + "name": "shard_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChunkDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch one chunk from a finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_chunk", + "x-fastnear-title": "NEAR Data API - Block Chunk" + } + }, + "/v0/block/{block_height}/headers": { + "get": { + "description": "Fetch only a finalized block's header and chunk summaries — no per-shard payload.", + "operationId": "get_block_headers", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockEnvelope", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch the block-level object for a finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_headers", + "x-fastnear-title": "NEAR Data API - Block Headers" + } + }, + "/v0/block/{block_height}/shard/{shard_id}": { + "get": { + "description": "Fetch one shard's full payload at a chosen block — chunk plus state changes and produced receipts.", + "operationId": "get_shard", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Shard ID to return.", + "example": "0", + "in": "path", + "name": "shard_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShardDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch one shard from a finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_shard", + "x-fastnear-title": "NEAR Data API - Block Shard" + } + }, + "/v0/block_opt/{block_height}": { + "get": { + "description": "Fetch an optimistic (not-yet-final) block at a chosen height — may redirect once the optimistic window has finalized.", + "operationId": "get_block_optimistic", + "parameters": [ + { + "description": "NEAR block height to retrieve.", + "example": "50000000", + "in": "path", + "name": "block_height", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Requested document, or `null` when the selected slice is absent" + }, + "302": { + "description": "Redirect to a canonical archive or finalized block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "404": { + "content": { + "application/json": { + "example": { + "error": "The block does not exist in this archive range", + "type": "BLOCK_DOES_NOT_EXIST" + }, + "schema": { + "$ref": "#/components/schemas/BlockErrorResponse" + } + } + }, + "description": "Structured block-height error" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Fetch an optimistic block by height", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "block_optimistic", + "x-fastnear-title": "NEAR Data API - Optimistic Block" + } + }, + "/v0/first_block": { + "get": { + "description": "Redirect to the chain's first post-genesis block — a starting cursor for indexers backfilling from the beginning.", + "operationId": "get_first_block", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Full block document returned after automatic redirect following" + }, + "302": { + "description": "Redirect to the canonical first block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + } + }, + "summary": "Redirect to the first block after genesis", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "first_block", + "x-fastnear-title": "NEAR Data API - First Block" + } + }, + "/v0/last_block/final": { + "get": { + "description": "Redirect to the most recent finalized block — the chain-tip cursor once consensus has settled.", + "operationId": "get_last_block_final", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Full block document returned after automatic redirect following" + }, + "302": { + "description": "Redirect to the latest block block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Redirect to the latest finalized block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "last_block_final", + "x-fastnear-title": "NEAR Data API - Last Final Block" + } + }, + "/v0/last_block/optimistic": { + "get": { + "description": "Redirect to the most recent optimistic block — the freshest-possible tip, ahead of final settlement.", + "operationId": "get_last_block_optimistic", + "parameters": [ + { + "description": "Optional FastNEAR subscription API key. Invalid values may return `401` before redirect handling.", + "in": "query", + "name": "apiKey", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockDocument", + "nullable": true + } + } + }, + "description": "Full block document returned after automatic redirect following" + }, + "302": { + "description": "Redirect to the latest block block URL", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "text/plain": { + "example": "Unauthorized", + "schema": { + "type": "string" + } + } + }, + "description": "Invalid or unauthorized API key" + }, + "500": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Cache or internal data error" + } + }, + "summary": "Redirect to the latest optimistic block", + "tags": [ + "blocks" + ], + "x-fastnear-slug": "last_block_optimistic", + "x-fastnear-title": "NEAR Data API - Last Optimistic Block" + } + } + }, + "servers": [ + { + "description": "Mainnet", + "url": "https://mainnet.neardata.xyz" + }, + { + "description": "Testnet", + "url": "https://testnet.neardata.xyz" + } + ] +} diff --git a/static/robots.txt b/static/robots.txt index 0b2008f..4b564e8 100644 --- a/static/robots.txt +++ b/static/robots.txt @@ -1,5 +1,6 @@ User-agent: * Allow: / +Content-Signal: search=yes, ai-input=yes, ai-train=yes User-agent: GPTBot Allow: / diff --git a/static/ru/agents.md b/static/ru/agents.md index 1acbaf4..6fff031 100644 --- a/static/ru/agents.md +++ b/static/ru/agents.md @@ -2,8 +2,6 @@ # Агенты на FastNear -{/* FASTNEAR_AI_DISCOVERY: Это операционная точка входа для ИИ-агентов, краулеров и сред автоматизации. Страница объясняет, какую информацию собрать до первого запроса, с какого API FastNear начинать, как устроена аутентификация между API и что должен содержать полезный ответ агента. */} - Эта страница — операционная точка входа для ИИ-агентов, краулеров и сред автоматизации, использующих FastNear. Цель простая: распознать реальную задачу пользователя, выбрать один API FastNear, получить минимальный полезный результат и расширяться на другой API только тогда, когда явно не хватает части ответа. ## Если нужен только следующий шаг @@ -11,6 +9,7 @@ - Нужно выбрать, с какого API FastNear начать? Используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces). - Нужны правила работы с учётными данными? Используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth). - Нужны примеры многошаговых сценариев? Используйте [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks). +- Нужно отдать FastNear в Claude Desktop, Codex, Cursor или другой MCP-клиент? Используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp). - Нужна точная документация по эндпоинту сейчас? Сразу откройте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx), [API переводов](https://docs.fastnear.com/ru/transfers), [NEAR Data API](https://docs.fastnear.com/ru/neardata) или [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). ## FastNear для агентов за минуту @@ -137,3 +136,4 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" - Нужна глубина маршрутизации и компромиссы? [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) - Нужен режим работы с учётными данными и обращение с секретами? [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) - Нужны примеры сценариев? [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) +- Нужно собрать собственную MCP-поверхность поверх FastNear? [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) diff --git a/static/ru/agents/auth.md b/static/ru/agents/auth.md index 3ddae5d..7cc2906 100644 --- a/static/ru/agents/auth.md +++ b/static/ru/agents/auth.md @@ -93,3 +93,4 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth) - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) - [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) diff --git a/static/ru/agents/auth/index.md b/static/ru/agents/auth/index.md index 3ddae5d..7cc2906 100644 --- a/static/ru/agents/auth/index.md +++ b/static/ru/agents/auth/index.md @@ -93,3 +93,4 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth) - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) - [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) diff --git a/static/ru/agents/choosing-surfaces.md b/static/ru/agents/choosing-surfaces.md index f78d329..f146f1e 100644 --- a/static/ru/agents/choosing-surfaces.md +++ b/static/ru/agents/choosing-surfaces.md @@ -2,12 +2,12 @@ # Как выбрать подходящую поверхность -{/* FASTNEAR_AI_DISCOVERY: Эта страница — для ИИ-агентов, которые выбирают поверхность FastNear по намерению пользователя. Она объясняет, как перейти от цели пользователя к лучшей первой поверхности, когда комбинировать поверхности и каких типичных ошибок избегать. */} - Не начинайте с того, чтобы отдавать агенту каждый эндпоинт FastNear. Сначала сформулируйте, какую задачу на самом деле хочет решить пользователь, а затем выберите один API или раздел справочника FastNear, который наиболее прямо отвечает на эту задачу. Для агента важнее не вопрос «какой эндпоинт существует?», а вопрос «какой ответ поможет пользователю сделать следующий шаг?». +Если ваша задача — спроектировать набор инструментов для MCP-сервера, а не маршрутизировать один пользовательский запрос, используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp). + ## Что определяет маршрут Прежде чем выбрать API, определите четыре вещи: @@ -255,3 +255,4 @@ - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) — полная карта поверхностей, базовые URL и подсказки по поглощению промптов. - [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — работа с учётными данными и операционный режим. - [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) — примеры многошаговых сценариев. +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) — рекомендуемый первый набор инструментов и TypeScript-пример на прямых HTTP-вызовах. diff --git a/static/ru/agents/choosing-surfaces/index.md b/static/ru/agents/choosing-surfaces/index.md index f78d329..f146f1e 100644 --- a/static/ru/agents/choosing-surfaces/index.md +++ b/static/ru/agents/choosing-surfaces/index.md @@ -2,12 +2,12 @@ # Как выбрать подходящую поверхность -{/* FASTNEAR_AI_DISCOVERY: Эта страница — для ИИ-агентов, которые выбирают поверхность FastNear по намерению пользователя. Она объясняет, как перейти от цели пользователя к лучшей первой поверхности, когда комбинировать поверхности и каких типичных ошибок избегать. */} - Не начинайте с того, чтобы отдавать агенту каждый эндпоинт FastNear. Сначала сформулируйте, какую задачу на самом деле хочет решить пользователь, а затем выберите один API или раздел справочника FastNear, который наиболее прямо отвечает на эту задачу. Для агента важнее не вопрос «какой эндпоинт существует?», а вопрос «какой ответ поможет пользователю сделать следующий шаг?». +Если ваша задача — спроектировать набор инструментов для MCP-сервера, а не маршрутизировать один пользовательский запрос, используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp). + ## Что определяет маршрут Прежде чем выбрать API, определите четыре вещи: @@ -255,3 +255,4 @@ - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) — полная карта поверхностей, базовые URL и подсказки по поглощению промптов. - [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — работа с учётными данными и операционный режим. - [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) — примеры многошаговых сценариев. +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) — рекомендуемый первый набор инструментов и TypeScript-пример на прямых HTTP-вызовах. diff --git a/static/ru/agents/index.md b/static/ru/agents/index.md index 1acbaf4..6fff031 100644 --- a/static/ru/agents/index.md +++ b/static/ru/agents/index.md @@ -2,8 +2,6 @@ # Агенты на FastNear -{/* FASTNEAR_AI_DISCOVERY: Это операционная точка входа для ИИ-агентов, краулеров и сред автоматизации. Страница объясняет, какую информацию собрать до первого запроса, с какого API FastNear начинать, как устроена аутентификация между API и что должен содержать полезный ответ агента. */} - Эта страница — операционная точка входа для ИИ-агентов, краулеров и сред автоматизации, использующих FastNear. Цель простая: распознать реальную задачу пользователя, выбрать один API FastNear, получить минимальный полезный результат и расширяться на другой API только тогда, когда явно не хватает части ответа. ## Если нужен только следующий шаг @@ -11,6 +9,7 @@ - Нужно выбрать, с какого API FastNear начать? Используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces). - Нужны правила работы с учётными данными? Используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth). - Нужны примеры многошаговых сценариев? Используйте [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks). +- Нужно отдать FastNear в Claude Desktop, Codex, Cursor или другой MCP-клиент? Используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp). - Нужна точная документация по эндпоинту сейчас? Сразу откройте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx), [API переводов](https://docs.fastnear.com/ru/transfers), [NEAR Data API](https://docs.fastnear.com/ru/neardata) или [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). ## FastNear для агентов за минуту @@ -137,3 +136,4 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" - Нужна глубина маршрутизации и компромиссы? [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) - Нужен режим работы с учётными данными и обращение с секретами? [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) - Нужны примеры сценариев? [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) +- Нужно собрать собственную MCP-поверхность поверх FastNear? [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) diff --git a/static/ru/agents/mcp.md b/static/ru/agents/mcp.md new file mode 100644 index 0000000..c18ebae --- /dev/null +++ b/static/ru/agents/mcp.md @@ -0,0 +1,471 @@ +**Источник:** [https://docs.fastnear.com/ru/agents/mcp](https://docs.fastnear.com/ru/agents/mcp) + +# Как построить MCP-сервер на FastNear + +Эта страница предназначена для команд, которым нужно отдать FastNear через собственный MCP-сервер для Claude Desktop, Codex, Cursor или другого MCP-клиента. `docs.fastnear.com` сам по себе не является MCP-сервером. Цель этой страницы — показать чистую стартовую точку поверх уже существующих RPC- и REST-API FastNear. + +## Когда MCP действительно нужен + +Стройте MCP-сервер, когда нужен один стабильный набор инструментов поверх API FastNear: + +- данные цепочки должны быть доступны как инструменты в desktop-клиенте или IDE +- нужно, чтобы агент сам выбирал между поверхностями аккаунта, транзакций, блоков и RPC +- требуется спрятать аутентификацию и базовые URL за небольшим доверенным бэкендом или локальным процессом +- нужен переиспользуемый контракт инструментов вместо того, чтобы каждый агент или сценарий напрямую вызывал сырой HTTP + +Если ваш сценарий и так контролирует HTTP и не нуждается в обнаружении инструментов, MCP может быть лишним. Документация и API FastNear уже хорошо работают как обычные HTTP-поверхности. + +## Рекомендуемый первый набор инструментов + +Начинайте с нескольких широких инструментов, а не с зеркалирования каждого эндпоинта: + +| MCP-инструмент | Поверхность FastNear | Для чего использовать | +| --- | --- | --- | +| `get-account-summary` | [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) | балансы, активы, стейкинг, сводки в стиле кошелька | +| `lookup-public-key` | [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key) | разрешение публичного ключа в один или несколько аккаунтов | +| `get-transactions-by-hash` | [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) | расследование транзакций и читаемое продолжение по исполнению | +| `get-latest-final-block` | [Last Final Block Redirect](https://docs.fastnear.com/ru/neardata/last-block-final) | проверки последней финализированной головы и сценарии опроса | +| `view-account-rpc` | [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) | точное каноническое состояние аккаунта, когда индексированной сводки недостаточно | + +Такой набор покрывает большинство сценариев «что есть у этого аккаунта?», «что произошло с этой транзакцией?» и «какой сейчас последний финализированный блок?» без требования понимать всю поверхность FastNear заранее. + +## Почему пример использует прямой HTTP + +Этот пример намеренно использует сырые вызовы `fetch()`, а не `near-api-js`. + +- Документация, которую вы читаете, устроена вокруг сырых RPC- и REST-контрактов. +- Прямой HTTP удерживает поведение MCP-инструментов в точном соответствии с документацией. +- Это помогает избежать пробелов абстракции SDK, версионного дрейфа и поведения вспомогательных функций, скрывающих реальный формат запросов и ответов. +- Для MCP-инструментов с упором на чтение прямой HTTP обычно оказывается самым простым рабочим решением. + +Если позже в том же процессе понадобятся подпись транзакций, вспомогательные функции для аккаунтов или сценарии, завязанные на кошелёк, `near-api-js` всё ещё может иметь смысл. Но для обучающего примера, сфокусированного на RPC и API FastNear, прямые вызовы — более чистый выбор по умолчанию. + +## Установка + +Используйте Node.js 20 или новее, чтобы глобальный `fetch()` уже был доступен. + +```bash +mkdir fastnear-mcp +cd fastnear-mcp +npm init -y +npm install @modelcontextprotocol/sdk zod +npm install -D tsx typescript @types/node +``` + +Официальная документация MCP TypeScript SDK сейчас рекомендует стабильную ветку `v1.x` для продовых сценариев. Команда установки выше подтянет текущий стабильный релиз пакета. + +## TypeScript-пример, который можно сразу брать за основу + +Создайте `fastnear-mcp.ts`: + +```ts title="fastnear-mcp.ts" + +type Network = "mainnet" | "testnet"; + +const DEFAULT_NETWORK: Network = + process.env.FASTNEAR_DEFAULT_NETWORK === "testnet" ? "testnet" : "mainnet"; + +const URLS: Record< + Network, + { + api: string; + rpc: string; + neardata: string; + tx: string; + } +> = { + mainnet: { + api: "https://api.fastnear.com", + rpc: "https://rpc.mainnet.fastnear.com", + neardata: "https://mainnet.neardata.xyz", + tx: "https://tx.main.fastnear.com", + }, + testnet: { + api: "https://test.api.fastnear.com", + rpc: "https://rpc.testnet.fastnear.com", + neardata: "https://testnet.neardata.xyz", + tx: "https://tx.test.fastnear.com", + }, +}; + +const apiKey = process.env.FASTNEAR_API_KEY; + +function selectNetwork(network?: Network): Network { + return network ?? DEFAULT_NETWORK; +} + +function authHeaders(extra: HeadersInit = {}): HeadersInit { + if (!apiKey) { + return extra; + } + + return { + ...extra, + Authorization: `Bearer ${apiKey}`, + }; +} + +async function requestJson(url: string, init?: RequestInit): Promise { + const response = await fetch(url, init); + const text = await response.text(); + + let body: unknown = null; + + if (text) { + try { + body = JSON.parse(text); + } catch { + body = text; + } + } + + if (!response.ok) { + const detail = + typeof body === "string" ? body : JSON.stringify(body, null, 2); + throw new Error(`${response.status} ${response.statusText}: ${detail}`); + } + + return body; +} + +function toolResult(data: unknown) { + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(data, null, 2), + }, + ], + }; +} + +function toolError(error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return { + isError: true, + content: [ + { + type: "text" as const, + text: message, + }, + ], + }; +} + +async function runTool(work: () => Promise) { + try { + return toolResult(await work()); + } catch (error) { + return toolError(error); + } +} + +function toIsoFromNanoseconds(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + + try { + return new Date(Number(BigInt(value) / 1_000_000n)).toISOString(); + } catch { + return null; + } +} + +async function getAccountSummary(network: Network, accountId: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/account/${encodeURIComponent(accountId)}/full`, + { + headers: authHeaders(), + }, + ); +} + +async function lookupPublicKey(network: Network, publicKey: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/public_key/${encodeURIComponent(publicKey)}`, + { + headers: authHeaders(), + }, + ); +} + +async function getTransactionsByHash(network: Network, txHashes: string[]) { + const baseUrl = URLS[network].tx; + return requestJson(`${baseUrl}/v0/transactions`, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + tx_hashes: txHashes, + }), + }); +} + +async function getLatestFinalBlock(network: Network) { + const baseUrl = URLS[network].neardata; + const result = (await requestJson(`${baseUrl}/v0/last_block/final`, { + headers: authHeaders(), + })) as { + block?: { + author?: string; + chunks?: unknown[]; + header?: { + height?: number; + hash?: string; + prev_hash?: string; + timestamp_nanosec?: string; + }; + }; + }; + + const block = result.block; + const header = block?.header; + + return { + network, + source: "NEAR Data API", + finality: "final", + block_height: header?.height ?? null, + block_hash: header?.hash ?? null, + prev_block_hash: header?.prev_hash ?? null, + author: block?.author ?? null, + timestamp_nanosec: header?.timestamp_nanosec ?? null, + timestamp_iso: toIsoFromNanoseconds(header?.timestamp_nanosec), + chunk_count: Array.isArray(block?.chunks) ? block.chunks.length : null, + }; +} + +async function viewAccountRpc( + network: Network, + accountId: string, + finality: "final" | "optimistic", +) { + const rpcUrl = URLS[network].rpc; + + return requestJson(rpcUrl, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + jsonrpc: "2.0", + id: `view-account:${accountId}`, + method: "query", + params: { + request_type: "view_account", + finality, + account_id: accountId, + }, + }), + }); +} + +const server = new McpServer({ + name: "fastnear-direct-http", + version: "0.1.0", +}); + +server.registerTool( + "get-account-summary", + { + title: "Get account summary", + description: + "Fetch a combined FastNear account view with balances, assets, and staking data.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, network }) => + runTool(() => getAccountSummary(selectNetwork(network), accountId)), +); + +server.registerTool( + "lookup-public-key", + { + title: "Lookup public key", + description: + "Resolve a public key to one or more NEAR accounts using the FastNear API.", + inputSchema: { + publicKey: z + .string() + .min(16) + .describe("Public key, for example ed25519:..."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ publicKey, network }) => + runTool(() => lookupPublicKey(selectNetwork(network), publicKey)), +); + +server.registerTool( + "get-transactions-by-hash", + { + title: "Get transactions by hash", + description: + "Fetch up to 20 transactions by hash from the Transactions API.", + inputSchema: { + txHashes: z + .array(z.string().min(32)) + .min(1) + .max(20) + .describe("One or more base58 transaction hashes."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ txHashes, network }) => + runTool(() => getTransactionsByHash(selectNetwork(network), txHashes)), +); + +server.registerTool( + "get-latest-final-block", + { + title: "Get latest final block", + description: + "Fetch a compact summary of the latest finalized block from the NEAR Data API.", + inputSchema: { + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ network }) => + runTool(() => getLatestFinalBlock(selectNetwork(network))), +); + +server.registerTool( + "view-account-rpc", + { + title: "View account via RPC", + description: + "Fetch canonical account state directly from NEAR JSON-RPC.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + finality: z + .enum(["final", "optimistic"]) + .optional() + .describe("Defaults to final."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, finality, network }) => + runTool(() => + viewAccountRpc( + selectNetwork(network), + accountId, + finality ?? "final", + ), + ), +); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); +``` + +Локальный запуск: + +```bash +FASTNEAR_API_KEY=your_key_here \ +FASTNEAR_DEFAULT_NETWORK=mainnet \ +npx tsx fastnear-mcp.ts +``` + +`FASTNEAR_API_KEY` для многих публичных чтений не обязателен, но для аутентифицированных рантаймов и трафика с повышенными лимитами это правильный режим по умолчанию. + +Вспомогательный маршрут `NEAR Data API` по пути `/v0/last_block/final` отвечает редиректом на маршрут текущего блока. Обычный `fetch()` проходит этот редирект автоматически, поэтому в примере не нужен отдельный код для обработки перенаправления. + +## Универсальная конфигурация клиента + +Эту страницу лучше держать универсальной. У большинства локальных MCP-клиентов одни и те же основные составляющие, даже если путь к конфигу или форма JSON немного отличаются: `command`, `args`, `env` и иногда `cwd`. + +Многие локальные MCP-клиенты принимают что-то очень похожее: + +```json title="Пример конфигурации MCP-клиента" +{ + "mcpServers": { + "fastnear": { + "command": "npx", + "args": ["tsx", "/absolute/path/to/fastnear-mcp.ts"], + "env": { + "FASTNEAR_API_KEY": "your_key_here", + "FASTNEAR_DEFAULT_NETWORK": "mainnet" + } + } + } +} +``` + +Если MCP-клиент запускает команды из другого рабочего каталога, задайте его опцию `cwd` или `workingDirectory`, если она поддерживается, либо замените `npx tsx` на абсолютный путь к локальному бинарнику `tsx`. Важно, чтобы клиент мог разрешить локально установленный пакет `tsx` до запуска `fastnear-mcp.ts`. + +Одного универсального примера конфигурации для такой страницы обычно достаточно. Фрагменты конфигурации под конкретные продукты и кнопки в духе «open in ...» меняются быстрее, чем сам контракт MCP-инструментов. + +Если позже понадобится удалённое или командное развёртывание, начните с этой же поверхности инструментов и только потом переходите от `stdio` к сетевому транспорту, когда действительно понадобится удалённый сервер. + +## Чеклист проектирования инструментов + +Когда вы превращаете FastNear в MCP-инструменты, эти значения по умолчанию обычно оказываются устойчивыми: + +- Называйте инструменты по задачам пользователя, а не по путям эндпоинтов. +- Начинайте с трёх-шести инструментов, а не с пятидесяти. +- Используйте [FastNear API](https://docs.fastnear.com/ru/api) для сводок и задач разрешения идентификаторов, [Transactions API](https://docs.fastnear.com/ru/tx) для читаемой истории, а [Справочник RPC](https://docs.fastnear.com/ru/rpc) — только когда важна каноническая семантика протокола. +- Делайте `network` опциональным, но явным, с разумным значением по умолчанию. +- Возвращайте компактный JSON. Не тяните огромные пэйлоады, если инструменту нужен только один срез ответа. +- Для инструментов, работающих в режиме опроса, вроде маршрутов по последнему блоку, по умолчанию лучше возвращать сводку, а не полное тело блока. +- Храните API-ключи в переменных окружения или менеджере секретов и подставляйте их на стороне сервера. +- Сохраняйте непрозрачные токены пагинации ровно в том виде, в каком их вернул FastNear. +- Ясно сообщайте вызывающей стороне, возвращает ли инструмент индексированную сводку или канонические данные RPC. +- Делайте так, чтобы один инструмент отвечал за один тип ответа. Не создавайте несколько перекрывающихся инструментов, которые частично решают одну и ту же задачу. + +## Частые ошибки + +- Не выставляйте каждый эндпоинт FastNear как отдельный MCP-инструмент в первый же день. +- Не заставляйте сценарии со сводкой по аккаунту идти через сырой RPC, если индексированное API уже отвечает на реальный вопрос пользователя. +- Не скрывайте разницу между индексированными данными и каноническими данными узла. +- Не кладите `FASTNEAR_API_KEY` в промпты, браузерное хранилище или закоммиченный конфиг. +- Не превращайте `NEAR Data API` в фальшивую потоковую абстракцию. Это поверхность чтения, ориентированная на явный опрос. + +## Полезные следующие расширения + +После того как базовый сервер заработал, следующими полезными инструментами обычно становятся: + +- история аккаунта поверх [Transactions API](https://docs.fastnear.com/ru/tx/account) +- история только переводов поверх [Transfers API](https://docs.fastnear.com/ru/transfers/query) +- `view`-вызовы контрактов поверх [Call a Function](https://docs.fastnear.com/ru/rpc/contract/call-function) +- небольшой `resource` или `prompt` с правилами маршрутизации и аутентификации именно вашей команды + +## Связанные руководства + +- [Агенты на FastNear](https://docs.fastnear.com/ru/agents) +- [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) +- [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/agents/mcp/index.md b/static/ru/agents/mcp/index.md new file mode 100644 index 0000000..c18ebae --- /dev/null +++ b/static/ru/agents/mcp/index.md @@ -0,0 +1,471 @@ +**Источник:** [https://docs.fastnear.com/ru/agents/mcp](https://docs.fastnear.com/ru/agents/mcp) + +# Как построить MCP-сервер на FastNear + +Эта страница предназначена для команд, которым нужно отдать FastNear через собственный MCP-сервер для Claude Desktop, Codex, Cursor или другого MCP-клиента. `docs.fastnear.com` сам по себе не является MCP-сервером. Цель этой страницы — показать чистую стартовую точку поверх уже существующих RPC- и REST-API FastNear. + +## Когда MCP действительно нужен + +Стройте MCP-сервер, когда нужен один стабильный набор инструментов поверх API FastNear: + +- данные цепочки должны быть доступны как инструменты в desktop-клиенте или IDE +- нужно, чтобы агент сам выбирал между поверхностями аккаунта, транзакций, блоков и RPC +- требуется спрятать аутентификацию и базовые URL за небольшим доверенным бэкендом или локальным процессом +- нужен переиспользуемый контракт инструментов вместо того, чтобы каждый агент или сценарий напрямую вызывал сырой HTTP + +Если ваш сценарий и так контролирует HTTP и не нуждается в обнаружении инструментов, MCP может быть лишним. Документация и API FastNear уже хорошо работают как обычные HTTP-поверхности. + +## Рекомендуемый первый набор инструментов + +Начинайте с нескольких широких инструментов, а не с зеркалирования каждого эндпоинта: + +| MCP-инструмент | Поверхность FastNear | Для чего использовать | +| --- | --- | --- | +| `get-account-summary` | [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) | балансы, активы, стейкинг, сводки в стиле кошелька | +| `lookup-public-key` | [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key) | разрешение публичного ключа в один или несколько аккаунтов | +| `get-transactions-by-hash` | [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) | расследование транзакций и читаемое продолжение по исполнению | +| `get-latest-final-block` | [Last Final Block Redirect](https://docs.fastnear.com/ru/neardata/last-block-final) | проверки последней финализированной головы и сценарии опроса | +| `view-account-rpc` | [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) | точное каноническое состояние аккаунта, когда индексированной сводки недостаточно | + +Такой набор покрывает большинство сценариев «что есть у этого аккаунта?», «что произошло с этой транзакцией?» и «какой сейчас последний финализированный блок?» без требования понимать всю поверхность FastNear заранее. + +## Почему пример использует прямой HTTP + +Этот пример намеренно использует сырые вызовы `fetch()`, а не `near-api-js`. + +- Документация, которую вы читаете, устроена вокруг сырых RPC- и REST-контрактов. +- Прямой HTTP удерживает поведение MCP-инструментов в точном соответствии с документацией. +- Это помогает избежать пробелов абстракции SDK, версионного дрейфа и поведения вспомогательных функций, скрывающих реальный формат запросов и ответов. +- Для MCP-инструментов с упором на чтение прямой HTTP обычно оказывается самым простым рабочим решением. + +Если позже в том же процессе понадобятся подпись транзакций, вспомогательные функции для аккаунтов или сценарии, завязанные на кошелёк, `near-api-js` всё ещё может иметь смысл. Но для обучающего примера, сфокусированного на RPC и API FastNear, прямые вызовы — более чистый выбор по умолчанию. + +## Установка + +Используйте Node.js 20 или новее, чтобы глобальный `fetch()` уже был доступен. + +```bash +mkdir fastnear-mcp +cd fastnear-mcp +npm init -y +npm install @modelcontextprotocol/sdk zod +npm install -D tsx typescript @types/node +``` + +Официальная документация MCP TypeScript SDK сейчас рекомендует стабильную ветку `v1.x` для продовых сценариев. Команда установки выше подтянет текущий стабильный релиз пакета. + +## TypeScript-пример, который можно сразу брать за основу + +Создайте `fastnear-mcp.ts`: + +```ts title="fastnear-mcp.ts" + +type Network = "mainnet" | "testnet"; + +const DEFAULT_NETWORK: Network = + process.env.FASTNEAR_DEFAULT_NETWORK === "testnet" ? "testnet" : "mainnet"; + +const URLS: Record< + Network, + { + api: string; + rpc: string; + neardata: string; + tx: string; + } +> = { + mainnet: { + api: "https://api.fastnear.com", + rpc: "https://rpc.mainnet.fastnear.com", + neardata: "https://mainnet.neardata.xyz", + tx: "https://tx.main.fastnear.com", + }, + testnet: { + api: "https://test.api.fastnear.com", + rpc: "https://rpc.testnet.fastnear.com", + neardata: "https://testnet.neardata.xyz", + tx: "https://tx.test.fastnear.com", + }, +}; + +const apiKey = process.env.FASTNEAR_API_KEY; + +function selectNetwork(network?: Network): Network { + return network ?? DEFAULT_NETWORK; +} + +function authHeaders(extra: HeadersInit = {}): HeadersInit { + if (!apiKey) { + return extra; + } + + return { + ...extra, + Authorization: `Bearer ${apiKey}`, + }; +} + +async function requestJson(url: string, init?: RequestInit): Promise { + const response = await fetch(url, init); + const text = await response.text(); + + let body: unknown = null; + + if (text) { + try { + body = JSON.parse(text); + } catch { + body = text; + } + } + + if (!response.ok) { + const detail = + typeof body === "string" ? body : JSON.stringify(body, null, 2); + throw new Error(`${response.status} ${response.statusText}: ${detail}`); + } + + return body; +} + +function toolResult(data: unknown) { + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(data, null, 2), + }, + ], + }; +} + +function toolError(error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return { + isError: true, + content: [ + { + type: "text" as const, + text: message, + }, + ], + }; +} + +async function runTool(work: () => Promise) { + try { + return toolResult(await work()); + } catch (error) { + return toolError(error); + } +} + +function toIsoFromNanoseconds(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + + try { + return new Date(Number(BigInt(value) / 1_000_000n)).toISOString(); + } catch { + return null; + } +} + +async function getAccountSummary(network: Network, accountId: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/account/${encodeURIComponent(accountId)}/full`, + { + headers: authHeaders(), + }, + ); +} + +async function lookupPublicKey(network: Network, publicKey: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/public_key/${encodeURIComponent(publicKey)}`, + { + headers: authHeaders(), + }, + ); +} + +async function getTransactionsByHash(network: Network, txHashes: string[]) { + const baseUrl = URLS[network].tx; + return requestJson(`${baseUrl}/v0/transactions`, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + tx_hashes: txHashes, + }), + }); +} + +async function getLatestFinalBlock(network: Network) { + const baseUrl = URLS[network].neardata; + const result = (await requestJson(`${baseUrl}/v0/last_block/final`, { + headers: authHeaders(), + })) as { + block?: { + author?: string; + chunks?: unknown[]; + header?: { + height?: number; + hash?: string; + prev_hash?: string; + timestamp_nanosec?: string; + }; + }; + }; + + const block = result.block; + const header = block?.header; + + return { + network, + source: "NEAR Data API", + finality: "final", + block_height: header?.height ?? null, + block_hash: header?.hash ?? null, + prev_block_hash: header?.prev_hash ?? null, + author: block?.author ?? null, + timestamp_nanosec: header?.timestamp_nanosec ?? null, + timestamp_iso: toIsoFromNanoseconds(header?.timestamp_nanosec), + chunk_count: Array.isArray(block?.chunks) ? block.chunks.length : null, + }; +} + +async function viewAccountRpc( + network: Network, + accountId: string, + finality: "final" | "optimistic", +) { + const rpcUrl = URLS[network].rpc; + + return requestJson(rpcUrl, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + jsonrpc: "2.0", + id: `view-account:${accountId}`, + method: "query", + params: { + request_type: "view_account", + finality, + account_id: accountId, + }, + }), + }); +} + +const server = new McpServer({ + name: "fastnear-direct-http", + version: "0.1.0", +}); + +server.registerTool( + "get-account-summary", + { + title: "Get account summary", + description: + "Fetch a combined FastNear account view with balances, assets, and staking data.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, network }) => + runTool(() => getAccountSummary(selectNetwork(network), accountId)), +); + +server.registerTool( + "lookup-public-key", + { + title: "Lookup public key", + description: + "Resolve a public key to one or more NEAR accounts using the FastNear API.", + inputSchema: { + publicKey: z + .string() + .min(16) + .describe("Public key, for example ed25519:..."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ publicKey, network }) => + runTool(() => lookupPublicKey(selectNetwork(network), publicKey)), +); + +server.registerTool( + "get-transactions-by-hash", + { + title: "Get transactions by hash", + description: + "Fetch up to 20 transactions by hash from the Transactions API.", + inputSchema: { + txHashes: z + .array(z.string().min(32)) + .min(1) + .max(20) + .describe("One or more base58 transaction hashes."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ txHashes, network }) => + runTool(() => getTransactionsByHash(selectNetwork(network), txHashes)), +); + +server.registerTool( + "get-latest-final-block", + { + title: "Get latest final block", + description: + "Fetch a compact summary of the latest finalized block from the NEAR Data API.", + inputSchema: { + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ network }) => + runTool(() => getLatestFinalBlock(selectNetwork(network))), +); + +server.registerTool( + "view-account-rpc", + { + title: "View account via RPC", + description: + "Fetch canonical account state directly from NEAR JSON-RPC.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + finality: z + .enum(["final", "optimistic"]) + .optional() + .describe("Defaults to final."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, finality, network }) => + runTool(() => + viewAccountRpc( + selectNetwork(network), + accountId, + finality ?? "final", + ), + ), +); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); +``` + +Локальный запуск: + +```bash +FASTNEAR_API_KEY=your_key_here \ +FASTNEAR_DEFAULT_NETWORK=mainnet \ +npx tsx fastnear-mcp.ts +``` + +`FASTNEAR_API_KEY` для многих публичных чтений не обязателен, но для аутентифицированных рантаймов и трафика с повышенными лимитами это правильный режим по умолчанию. + +Вспомогательный маршрут `NEAR Data API` по пути `/v0/last_block/final` отвечает редиректом на маршрут текущего блока. Обычный `fetch()` проходит этот редирект автоматически, поэтому в примере не нужен отдельный код для обработки перенаправления. + +## Универсальная конфигурация клиента + +Эту страницу лучше держать универсальной. У большинства локальных MCP-клиентов одни и те же основные составляющие, даже если путь к конфигу или форма JSON немного отличаются: `command`, `args`, `env` и иногда `cwd`. + +Многие локальные MCP-клиенты принимают что-то очень похожее: + +```json title="Пример конфигурации MCP-клиента" +{ + "mcpServers": { + "fastnear": { + "command": "npx", + "args": ["tsx", "/absolute/path/to/fastnear-mcp.ts"], + "env": { + "FASTNEAR_API_KEY": "your_key_here", + "FASTNEAR_DEFAULT_NETWORK": "mainnet" + } + } + } +} +``` + +Если MCP-клиент запускает команды из другого рабочего каталога, задайте его опцию `cwd` или `workingDirectory`, если она поддерживается, либо замените `npx tsx` на абсолютный путь к локальному бинарнику `tsx`. Важно, чтобы клиент мог разрешить локально установленный пакет `tsx` до запуска `fastnear-mcp.ts`. + +Одного универсального примера конфигурации для такой страницы обычно достаточно. Фрагменты конфигурации под конкретные продукты и кнопки в духе «open in ...» меняются быстрее, чем сам контракт MCP-инструментов. + +Если позже понадобится удалённое или командное развёртывание, начните с этой же поверхности инструментов и только потом переходите от `stdio` к сетевому транспорту, когда действительно понадобится удалённый сервер. + +## Чеклист проектирования инструментов + +Когда вы превращаете FastNear в MCP-инструменты, эти значения по умолчанию обычно оказываются устойчивыми: + +- Называйте инструменты по задачам пользователя, а не по путям эндпоинтов. +- Начинайте с трёх-шести инструментов, а не с пятидесяти. +- Используйте [FastNear API](https://docs.fastnear.com/ru/api) для сводок и задач разрешения идентификаторов, [Transactions API](https://docs.fastnear.com/ru/tx) для читаемой истории, а [Справочник RPC](https://docs.fastnear.com/ru/rpc) — только когда важна каноническая семантика протокола. +- Делайте `network` опциональным, но явным, с разумным значением по умолчанию. +- Возвращайте компактный JSON. Не тяните огромные пэйлоады, если инструменту нужен только один срез ответа. +- Для инструментов, работающих в режиме опроса, вроде маршрутов по последнему блоку, по умолчанию лучше возвращать сводку, а не полное тело блока. +- Храните API-ключи в переменных окружения или менеджере секретов и подставляйте их на стороне сервера. +- Сохраняйте непрозрачные токены пагинации ровно в том виде, в каком их вернул FastNear. +- Ясно сообщайте вызывающей стороне, возвращает ли инструмент индексированную сводку или канонические данные RPC. +- Делайте так, чтобы один инструмент отвечал за один тип ответа. Не создавайте несколько перекрывающихся инструментов, которые частично решают одну и ту же задачу. + +## Частые ошибки + +- Не выставляйте каждый эндпоинт FastNear как отдельный MCP-инструмент в первый же день. +- Не заставляйте сценарии со сводкой по аккаунту идти через сырой RPC, если индексированное API уже отвечает на реальный вопрос пользователя. +- Не скрывайте разницу между индексированными данными и каноническими данными узла. +- Не кладите `FASTNEAR_API_KEY` в промпты, браузерное хранилище или закоммиченный конфиг. +- Не превращайте `NEAR Data API` в фальшивую потоковую абстракцию. Это поверхность чтения, ориентированная на явный опрос. + +## Полезные следующие расширения + +После того как базовый сервер заработал, следующими полезными инструментами обычно становятся: + +- история аккаунта поверх [Transactions API](https://docs.fastnear.com/ru/tx/account) +- история только переводов поверх [Transfers API](https://docs.fastnear.com/ru/transfers/query) +- `view`-вызовы контрактов поверх [Call a Function](https://docs.fastnear.com/ru/rpc/contract/call-function) +- небольшой `resource` или `prompt` с правилами маршрутизации и аутентификации именно вашей команды + +## Связанные руководства + +- [Агенты на FastNear](https://docs.fastnear.com/ru/agents) +- [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) +- [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) diff --git a/static/ru/agents/playbooks.md b/static/ru/agents/playbooks.md index fa1cd05..5696b52 100644 --- a/static/ru/agents/playbooks.md +++ b/static/ru/agents/playbooks.md @@ -2,8 +2,6 @@ # Плейбуки для агентов -{/* FASTNEAR_AI_DISCOVERY: Эта страница даёт ИИ-агентам конкретные многошаговые сценарии для типовых задач FastNear. Каждый плейбук называет минимальные входы, первый API, с которого начать, момент для расширения на другой API и то, что должен содержать полезный ответ. */} - Используйте эту страницу, когда агент уже знает, какой тип задачи он обрабатывает, и ему нужны ближайшие шаги по умолчанию. Каждый плейбук начинается с одного API FastNear, называет минимально полезные входы и подсказывает, когда остановиться, а когда расширяться. Базовое правило остаётся одним для всех плейбуков: начните с одного API, получите минимальный полезный результат и расширяйтесь, только когда можете точно назвать недостающую часть. @@ -259,4 +257,5 @@ - используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces), чтобы выбрать первый API - используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth), если блокер — работа с учётными данными +- используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp), если вы проектируете переиспользуемую MCP-поверхность, а не отвечаете на одну пользовательскую задачу - возвращайтесь к [Агентам на FastNear](https://docs.fastnear.com/ru/agents) за правилами рабочего цикла по умолчанию и формы ответа diff --git a/static/ru/agents/playbooks/index.md b/static/ru/agents/playbooks/index.md index fa1cd05..5696b52 100644 --- a/static/ru/agents/playbooks/index.md +++ b/static/ru/agents/playbooks/index.md @@ -2,8 +2,6 @@ # Плейбуки для агентов -{/* FASTNEAR_AI_DISCOVERY: Эта страница даёт ИИ-агентам конкретные многошаговые сценарии для типовых задач FastNear. Каждый плейбук называет минимальные входы, первый API, с которого начать, момент для расширения на другой API и то, что должен содержать полезный ответ. */} - Используйте эту страницу, когда агент уже знает, какой тип задачи он обрабатывает, и ему нужны ближайшие шаги по умолчанию. Каждый плейбук начинается с одного API FastNear, называет минимально полезные входы и подсказывает, когда остановиться, а когда расширяться. Базовое правило остаётся одним для всех плейбуков: начните с одного API, получите минимальный полезный результат и расширяйтесь, только когда можете точно назвать недостающую часть. @@ -259,4 +257,5 @@ - используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces), чтобы выбрать первый API - используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth), если блокер — работа с учётными данными +- используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp), если вы проектируете переиспользуемую MCP-поверхность, а не отвечаете на одну пользовательскую задачу - возвращайтесь к [Агентам на FastNear](https://docs.fastnear.com/ru/agents) за правилами рабочего цикла по умолчанию и формы ответа diff --git a/static/ru/guides/llms.txt b/static/ru/guides/llms.txt index b2bad97..1bf30ea 100644 --- a/static/ru/guides/llms.txt +++ b/static/ru/guides/llms.txt @@ -23,6 +23,7 @@ - [Агенты на FastNear](https://docs.fastnear.com/ru/agents.md): Операционное руководство для ИИ-агентов и сред автоматизации, использующих документацию FastNear и её RPC- и REST-API. - [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth.md): Операционная работа с учётными данными для ИИ-агентов, автоматизаций, воркеров и бэкенд-сред, использующих FastNear. - [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces.md): Направьте задачу агента к сырому RPC, индексированным API, истории транзакций, NEAR Data, FastData или снапшотам. +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp.md): Используйте RPC и REST API FastNear внутри собственного MCP-сервера на TypeScript с примером на прямых HTTP-вызовах. - [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks.md): Рабочие процессы FastNear по умолчанию для типовых задач ИИ-агентов и автоматизаций. ## Другие гайды @@ -32,6 +33,6 @@ ## Снапшоты -- [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [mainnet](https://docs.fastnear.com/ru/snapshots/mainnet.md): Скачайте RPC- и архивные снапшоты mainnet для быстрого развёртывания NEAR-инфраструктуры на базе FastNear. -- [testnet](https://docs.fastnear.com/ru/snapshots/testnet.md): Скачайте RPC- и архивные снапшоты testnet для быстрого развёртывания NEAR-инфраструктуры на базе FastNear. +- [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Текущий статус снапшотов FastNear и рекомендации для подъёма узлов NEAR и запросов архивных снапшотов. +- [mainnet](https://docs.fastnear.com/ru/snapshots/mainnet.md): Текущая позиция FastNear по снапшотам mainnet для RPC-подъёма и запросов архивных снапшотов. +- [testnet](https://docs.fastnear.com/ru/snapshots/testnet.md): Текущая позиция FastNear по снапшотам testnet для RPC-подъёма и запросов архивных снапшотов. diff --git a/static/ru/llms-full.txt b/static/ru/llms-full.txt index ae95642..a32daaf 100644 --- a/static/ru/llms-full.txt +++ b/static/ru/llms-full.txt @@ -138,8 +138,6 @@ AI-читабельные Markdown-копии авторских гайдов и # Агенты на FastNear -{/* FASTNEAR_AI_DISCOVERY: Это операционная точка входа для ИИ-агентов, краулеров и сред автоматизации. Страница объясняет, какую информацию собрать до первого запроса, с какого API FastNear начинать, как устроена аутентификация между API и что должен содержать полезный ответ агента. */} - Эта страница — операционная точка входа для ИИ-агентов, краулеров и сред автоматизации, использующих FastNear. Цель простая: распознать реальную задачу пользователя, выбрать один API FastNear, получить минимальный полезный результат и расширяться на другой API только тогда, когда явно не хватает части ответа. ## Если нужен только следующий шаг @@ -147,6 +145,7 @@ AI-читабельные Markdown-копии авторских гайдов и - Нужно выбрать, с какого API FastNear начать? Используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces). - Нужны правила работы с учётными данными? Используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth). - Нужны примеры многошаговых сценариев? Используйте [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks). +- Нужно отдать FastNear в Claude Desktop, Codex, Cursor или другой MCP-клиент? Используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp). - Нужна точная документация по эндпоинту сейчас? Сразу откройте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx), [API переводов](https://docs.fastnear.com/ru/transfers), [NEAR Data API](https://docs.fastnear.com/ru/neardata) или [KV FastData API](https://docs.fastnear.com/ru/fastdata/kv). ## FastNear для агентов за минуту @@ -273,6 +272,7 @@ curl "https://rpc.mainnet.fastnear.com?apiKey=${API_KEY}" - Нужна глубина маршрутизации и компромиссы? [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) - Нужен режим работы с учётными данными и обращение с секретами? [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) - Нужны примеры сценариев? [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) +- Нужно собрать собственную MCP-поверхность поверх FastNear? [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) --- @@ -376,6 +376,7 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - [Аутентификация и доступ](https://docs.fastnear.com/ru/auth) - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) - [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) --- @@ -388,12 +389,12 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { # Как выбрать подходящую поверхность -{/* FASTNEAR_AI_DISCOVERY: Эта страница — для ИИ-агентов, которые выбирают поверхность FastNear по намерению пользователя. Она объясняет, как перейти от цели пользователя к лучшей первой поверхности, когда комбинировать поверхности и каких типичных ошибок избегать. */} - Не начинайте с того, чтобы отдавать агенту каждый эндпоинт FastNear. Сначала сформулируйте, какую задачу на самом деле хочет решить пользователь, а затем выберите один API или раздел справочника FastNear, который наиболее прямо отвечает на эту задачу. Для агента важнее не вопрос «какой эндпоинт существует?», а вопрос «какой ответ поможет пользователю сделать следующий шаг?». +Если ваша задача — спроектировать набор инструментов для MCP-сервера, а не маршрутизировать один пользовательский запрос, используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp). + ## Что определяет маршрут Прежде чем выбрать API, определите четыре вещи: @@ -641,6 +642,486 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - [Агенты на FastNear](https://docs.fastnear.com/ru/agents) — полная карта поверхностей, базовые URL и подсказки по поглощению промптов. - [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) — работа с учётными данными и операционный режим. - [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) — примеры многошаговых сценариев. +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp) — рекомендуемый первый набор инструментов и TypeScript-пример на прямых HTTP-вызовах. + +--- + +## Как построить MCP-сервер на FastNear + +- HTML-маршрут: https://docs.fastnear.com/ru/agents/mcp +- Markdown-маршрут: https://docs.fastnear.com/ru/agents/mcp.md + +**Источник:** [https://docs.fastnear.com/ru/agents/mcp](https://docs.fastnear.com/ru/agents/mcp) + +# Как построить MCP-сервер на FastNear + +Эта страница предназначена для команд, которым нужно отдать FastNear через собственный MCP-сервер для Claude Desktop, Codex, Cursor или другого MCP-клиента. `docs.fastnear.com` сам по себе не является MCP-сервером. Цель этой страницы — показать чистую стартовую точку поверх уже существующих RPC- и REST-API FastNear. + +## Когда MCP действительно нужен + +Стройте MCP-сервер, когда нужен один стабильный набор инструментов поверх API FastNear: + +- данные цепочки должны быть доступны как инструменты в desktop-клиенте или IDE +- нужно, чтобы агент сам выбирал между поверхностями аккаунта, транзакций, блоков и RPC +- требуется спрятать аутентификацию и базовые URL за небольшим доверенным бэкендом или локальным процессом +- нужен переиспользуемый контракт инструментов вместо того, чтобы каждый агент или сценарий напрямую вызывал сырой HTTP + +Если ваш сценарий и так контролирует HTTP и не нуждается в обнаружении инструментов, MCP может быть лишним. Документация и API FastNear уже хорошо работают как обычные HTTP-поверхности. + +## Рекомендуемый первый набор инструментов + +Начинайте с нескольких широких инструментов, а не с зеркалирования каждого эндпоинта: + +| MCP-инструмент | Поверхность FastNear | Для чего использовать | +| --- | --- | --- | +| `get-account-summary` | [V1 Full Account View](https://docs.fastnear.com/ru/api/v1/account-full) | балансы, активы, стейкинг, сводки в стиле кошелька | +| `lookup-public-key` | [V1 Public Key Lookup](https://docs.fastnear.com/ru/api/v1/public-key) | разрешение публичного ключа в один или несколько аккаунтов | +| `get-transactions-by-hash` | [Transactions by Hash](https://docs.fastnear.com/ru/tx/transactions) | расследование транзакций и читаемое продолжение по исполнению | +| `get-latest-final-block` | [Last Final Block Redirect](https://docs.fastnear.com/ru/neardata/last-block-final) | проверки последней финализированной головы и сценарии опроса | +| `view-account-rpc` | [View Account](https://docs.fastnear.com/ru/rpc/account/view-account) | точное каноническое состояние аккаунта, когда индексированной сводки недостаточно | + +Такой набор покрывает большинство сценариев «что есть у этого аккаунта?», «что произошло с этой транзакцией?» и «какой сейчас последний финализированный блок?» без требования понимать всю поверхность FastNear заранее. + +## Почему пример использует прямой HTTP + +Этот пример намеренно использует сырые вызовы `fetch()`, а не `near-api-js`. + +- Документация, которую вы читаете, устроена вокруг сырых RPC- и REST-контрактов. +- Прямой HTTP удерживает поведение MCP-инструментов в точном соответствии с документацией. +- Это помогает избежать пробелов абстракции SDK, версионного дрейфа и поведения вспомогательных функций, скрывающих реальный формат запросов и ответов. +- Для MCP-инструментов с упором на чтение прямой HTTP обычно оказывается самым простым рабочим решением. + +Если позже в том же процессе понадобятся подпись транзакций, вспомогательные функции для аккаунтов или сценарии, завязанные на кошелёк, `near-api-js` всё ещё может иметь смысл. Но для обучающего примера, сфокусированного на RPC и API FastNear, прямые вызовы — более чистый выбор по умолчанию. + +## Установка + +Используйте Node.js 20 или новее, чтобы глобальный `fetch()` уже был доступен. + +```bash +mkdir fastnear-mcp +cd fastnear-mcp +npm init -y +npm install @modelcontextprotocol/sdk zod +npm install -D tsx typescript @types/node +``` + +Официальная документация MCP TypeScript SDK сейчас рекомендует стабильную ветку `v1.x` для продовых сценариев. Команда установки выше подтянет текущий стабильный релиз пакета. + +## TypeScript-пример, который можно сразу брать за основу + +Создайте `fastnear-mcp.ts`: + +```ts title="fastnear-mcp.ts" + +type Network = "mainnet" | "testnet"; + +const DEFAULT_NETWORK: Network = + process.env.FASTNEAR_DEFAULT_NETWORK === "testnet" ? "testnet" : "mainnet"; + +const URLS: Record< + Network, + { + api: string; + rpc: string; + neardata: string; + tx: string; + } +> = { + mainnet: { + api: "https://api.fastnear.com", + rpc: "https://rpc.mainnet.fastnear.com", + neardata: "https://mainnet.neardata.xyz", + tx: "https://tx.main.fastnear.com", + }, + testnet: { + api: "https://test.api.fastnear.com", + rpc: "https://rpc.testnet.fastnear.com", + neardata: "https://testnet.neardata.xyz", + tx: "https://tx.test.fastnear.com", + }, +}; + +const apiKey = process.env.FASTNEAR_API_KEY; + +function selectNetwork(network?: Network): Network { + return network ?? DEFAULT_NETWORK; +} + +function authHeaders(extra: HeadersInit = {}): HeadersInit { + if (!apiKey) { + return extra; + } + + return { + ...extra, + Authorization: `Bearer ${apiKey}`, + }; +} + +async function requestJson(url: string, init?: RequestInit): Promise { + const response = await fetch(url, init); + const text = await response.text(); + + let body: unknown = null; + + if (text) { + try { + body = JSON.parse(text); + } catch { + body = text; + } + } + + if (!response.ok) { + const detail = + typeof body === "string" ? body : JSON.stringify(body, null, 2); + throw new Error(`${response.status} ${response.statusText}: ${detail}`); + } + + return body; +} + +function toolResult(data: unknown) { + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(data, null, 2), + }, + ], + }; +} + +function toolError(error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return { + isError: true, + content: [ + { + type: "text" as const, + text: message, + }, + ], + }; +} + +async function runTool(work: () => Promise) { + try { + return toolResult(await work()); + } catch (error) { + return toolError(error); + } +} + +function toIsoFromNanoseconds(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + + try { + return new Date(Number(BigInt(value) / 1_000_000n)).toISOString(); + } catch { + return null; + } +} + +async function getAccountSummary(network: Network, accountId: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/account/${encodeURIComponent(accountId)}/full`, + { + headers: authHeaders(), + }, + ); +} + +async function lookupPublicKey(network: Network, publicKey: string) { + const baseUrl = URLS[network].api; + return requestJson( + `${baseUrl}/v1/public_key/${encodeURIComponent(publicKey)}`, + { + headers: authHeaders(), + }, + ); +} + +async function getTransactionsByHash(network: Network, txHashes: string[]) { + const baseUrl = URLS[network].tx; + return requestJson(`${baseUrl}/v0/transactions`, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + tx_hashes: txHashes, + }), + }); +} + +async function getLatestFinalBlock(network: Network) { + const baseUrl = URLS[network].neardata; + const result = (await requestJson(`${baseUrl}/v0/last_block/final`, { + headers: authHeaders(), + })) as { + block?: { + author?: string; + chunks?: unknown[]; + header?: { + height?: number; + hash?: string; + prev_hash?: string; + timestamp_nanosec?: string; + }; + }; + }; + + const block = result.block; + const header = block?.header; + + return { + network, + source: "NEAR Data API", + finality: "final", + block_height: header?.height ?? null, + block_hash: header?.hash ?? null, + prev_block_hash: header?.prev_hash ?? null, + author: block?.author ?? null, + timestamp_nanosec: header?.timestamp_nanosec ?? null, + timestamp_iso: toIsoFromNanoseconds(header?.timestamp_nanosec), + chunk_count: Array.isArray(block?.chunks) ? block.chunks.length : null, + }; +} + +async function viewAccountRpc( + network: Network, + accountId: string, + finality: "final" | "optimistic", +) { + const rpcUrl = URLS[network].rpc; + + return requestJson(rpcUrl, { + method: "POST", + headers: authHeaders({ + "Content-Type": "application/json", + }), + body: JSON.stringify({ + jsonrpc: "2.0", + id: `view-account:${accountId}`, + method: "query", + params: { + request_type: "view_account", + finality, + account_id: accountId, + }, + }), + }); +} + +const server = new McpServer({ + name: "fastnear-direct-http", + version: "0.1.0", +}); + +server.registerTool( + "get-account-summary", + { + title: "Get account summary", + description: + "Fetch a combined FastNear account view with balances, assets, and staking data.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, network }) => + runTool(() => getAccountSummary(selectNetwork(network), accountId)), +); + +server.registerTool( + "lookup-public-key", + { + title: "Lookup public key", + description: + "Resolve a public key to one or more NEAR accounts using the FastNear API.", + inputSchema: { + publicKey: z + .string() + .min(16) + .describe("Public key, for example ed25519:..."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ publicKey, network }) => + runTool(() => lookupPublicKey(selectNetwork(network), publicKey)), +); + +server.registerTool( + "get-transactions-by-hash", + { + title: "Get transactions by hash", + description: + "Fetch up to 20 transactions by hash from the Transactions API.", + inputSchema: { + txHashes: z + .array(z.string().min(32)) + .min(1) + .max(20) + .describe("One or more base58 transaction hashes."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ txHashes, network }) => + runTool(() => getTransactionsByHash(selectNetwork(network), txHashes)), +); + +server.registerTool( + "get-latest-final-block", + { + title: "Get latest final block", + description: + "Fetch a compact summary of the latest finalized block from the NEAR Data API.", + inputSchema: { + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ network }) => + runTool(() => getLatestFinalBlock(selectNetwork(network))), +); + +server.registerTool( + "view-account-rpc", + { + title: "View account via RPC", + description: + "Fetch canonical account state directly from NEAR JSON-RPC.", + inputSchema: { + accountId: z + .string() + .min(2) + .describe("NEAR account ID, for example fastnear.near"), + finality: z + .enum(["final", "optimistic"]) + .optional() + .describe("Defaults to final."), + network: z + .enum(["mainnet", "testnet"]) + .optional() + .describe("Defaults to FASTNEAR_DEFAULT_NETWORK or mainnet."), + }, + }, + async ({ accountId, finality, network }) => + runTool(() => + viewAccountRpc( + selectNetwork(network), + accountId, + finality ?? "final", + ), + ), +); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); +``` + +Локальный запуск: + +```bash +FASTNEAR_API_KEY=your_key_here \ +FASTNEAR_DEFAULT_NETWORK=mainnet \ +npx tsx fastnear-mcp.ts +``` + +`FASTNEAR_API_KEY` для многих публичных чтений не обязателен, но для аутентифицированных рантаймов и трафика с повышенными лимитами это правильный режим по умолчанию. + +Вспомогательный маршрут `NEAR Data API` по пути `/v0/last_block/final` отвечает редиректом на маршрут текущего блока. Обычный `fetch()` проходит этот редирект автоматически, поэтому в примере не нужен отдельный код для обработки перенаправления. + +## Универсальная конфигурация клиента + +Эту страницу лучше держать универсальной. У большинства локальных MCP-клиентов одни и те же основные составляющие, даже если путь к конфигу или форма JSON немного отличаются: `command`, `args`, `env` и иногда `cwd`. + +Многие локальные MCP-клиенты принимают что-то очень похожее: + +```json title="Пример конфигурации MCP-клиента" +{ + "mcpServers": { + "fastnear": { + "command": "npx", + "args": ["tsx", "/absolute/path/to/fastnear-mcp.ts"], + "env": { + "FASTNEAR_API_KEY": "your_key_here", + "FASTNEAR_DEFAULT_NETWORK": "mainnet" + } + } + } +} +``` + +Если MCP-клиент запускает команды из другого рабочего каталога, задайте его опцию `cwd` или `workingDirectory`, если она поддерживается, либо замените `npx tsx` на абсолютный путь к локальному бинарнику `tsx`. Важно, чтобы клиент мог разрешить локально установленный пакет `tsx` до запуска `fastnear-mcp.ts`. + +Одного универсального примера конфигурации для такой страницы обычно достаточно. Фрагменты конфигурации под конкретные продукты и кнопки в духе «open in ...» меняются быстрее, чем сам контракт MCP-инструментов. + +Если позже понадобится удалённое или командное развёртывание, начните с этой же поверхности инструментов и только потом переходите от `stdio` к сетевому транспорту, когда действительно понадобится удалённый сервер. + +## Чеклист проектирования инструментов + +Когда вы превращаете FastNear в MCP-инструменты, эти значения по умолчанию обычно оказываются устойчивыми: + +- Называйте инструменты по задачам пользователя, а не по путям эндпоинтов. +- Начинайте с трёх-шести инструментов, а не с пятидесяти. +- Используйте [FastNear API](https://docs.fastnear.com/ru/api) для сводок и задач разрешения идентификаторов, [Transactions API](https://docs.fastnear.com/ru/tx) для читаемой истории, а [Справочник RPC](https://docs.fastnear.com/ru/rpc) — только когда важна каноническая семантика протокола. +- Делайте `network` опциональным, но явным, с разумным значением по умолчанию. +- Возвращайте компактный JSON. Не тяните огромные пэйлоады, если инструменту нужен только один срез ответа. +- Для инструментов, работающих в режиме опроса, вроде маршрутов по последнему блоку, по умолчанию лучше возвращать сводку, а не полное тело блока. +- Храните API-ключи в переменных окружения или менеджере секретов и подставляйте их на стороне сервера. +- Сохраняйте непрозрачные токены пагинации ровно в том виде, в каком их вернул FastNear. +- Ясно сообщайте вызывающей стороне, возвращает ли инструмент индексированную сводку или канонические данные RPC. +- Делайте так, чтобы один инструмент отвечал за один тип ответа. Не создавайте несколько перекрывающихся инструментов, которые частично решают одну и ту же задачу. + +## Частые ошибки + +- Не выставляйте каждый эндпоинт FastNear как отдельный MCP-инструмент в первый же день. +- Не заставляйте сценарии со сводкой по аккаунту идти через сырой RPC, если индексированное API уже отвечает на реальный вопрос пользователя. +- Не скрывайте разницу между индексированными данными и каноническими данными узла. +- Не кладите `FASTNEAR_API_KEY` в промпты, браузерное хранилище или закоммиченный конфиг. +- Не превращайте `NEAR Data API` в фальшивую потоковую абстракцию. Это поверхность чтения, ориентированная на явный опрос. + +## Полезные следующие расширения + +После того как базовый сервер заработал, следующими полезными инструментами обычно становятся: + +- история аккаунта поверх [Transactions API](https://docs.fastnear.com/ru/tx/account) +- история только переводов поверх [Transfers API](https://docs.fastnear.com/ru/transfers/query) +- `view`-вызовы контрактов поверх [Call a Function](https://docs.fastnear.com/ru/rpc/contract/call-function) +- небольшой `resource` или `prompt` с правилами маршрутизации и аутентификации именно вашей команды + +## Связанные руководства + +- [Агенты на FastNear](https://docs.fastnear.com/ru/agents) +- [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces) +- [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth) +- [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks) --- @@ -653,8 +1134,6 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { # Плейбуки для агентов -{/* FASTNEAR_AI_DISCOVERY: Эта страница даёт ИИ-агентам конкретные многошаговые сценарии для типовых задач FastNear. Каждый плейбук называет минимальные входы, первый API, с которого начать, момент для расширения на другой API и то, что должен содержать полезный ответ. */} - Используйте эту страницу, когда агент уже знает, какой тип задачи он обрабатывает, и ему нужны ближайшие шаги по умолчанию. Каждый плейбук начинается с одного API FastNear, называет минимально полезные входы и подсказывает, когда остановиться, а когда расширяться. Базовое правило остаётся одним для всех плейбуков: начните с одного API, получите минимальный полезный результат и расширяйтесь, только когда можете точно назвать недостающую часть. @@ -910,6 +1389,7 @@ const response = await fetch('https://rpc.mainnet.fastnear.com', { - используйте [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces), чтобы выбрать первый API - используйте [Аутентификацию для агентов](https://docs.fastnear.com/ru/agents/auth), если блокер — работа с учётными данными +- используйте [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp), если вы проектируете переиспользуемую MCP-поверхность, а не отвечаете на одну пользовательскую задачу - возвращайтесь к [Агентам на FastNear](https://docs.fastnear.com/ru/agents) за правилами рабочего цикла по умолчанию и формы ответа --- @@ -1639,50 +2119,51 @@ https://archival-rpc.testnet.fastnear.com Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. :::warning[Бесплатные снапшоты устарели] -Бесплатные снапшоты данных nearcore больше не выпускаются. +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). ::: -## Используйте этот раздел, когда +## Текущий статус -- нужно поднять узел mainnet или testnet из данных снапшота -- идёт восстановление RPC- или архивного узла -- уже известно, что нужен путь загрузки снапшота FastNear +- Сейчас FastNear не публикует через этот сайт рабочий публичный путь для самостоятельной загрузки снапшотов. +- Для обычного запуска RPC-узлов и валидаторов используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему Epoch Sync вместе с децентрализованной синхронизацией состояния. +- Для доступа к архивным снапшотам переходите на [страницу FastNear о снапшотах](https://fastnear.com/snapshots) или пишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com). +- Исторические скрипты из [fastnear/static](https://github.com/fastnear/static) по-прежнему существуют, но прежний публичный поток с `latest.txt` больше не является поддерживаемым публичным сценарием. -## Не используйте этот раздел, когда +## Полезные материалы для операторов -- идёт запрос данных цепочки для приложения -- нужны свежие блоки, балансы, история или состояние контракта -- нужны общие рекомендации по продуктовому API, а не настройка оператором +- [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync) — актуальный шаг для обновления списка boot nodes перед первым запуском. +- [NEAR Nodes: синхронизация состояния](https://near-nodes.io/rpc/state-sync) — текущая рекомендуемая схема запуска без снапшота. +- [NEAR Nodes: запуск архивного узла](https://near-nodes.io/archival/run-archival-node) — настройка архивного узла и ожидания по каталогу данных. +- [NEAR Nodes: раздельное хранилище для архивных узлов](https://near-nodes.io/archival/split-storage-archival) — если вы планируете современную схему с раздельным горячим и холодным хранилищем. -В этих случаях используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx) или [NEAR Data API](https://docs.fastnear.com/ru/neardata). - -## Перед загрузкой +## Что стоит подготовить до запроса архивного снапшота -- Сначала выберите сеть: mainnet или testnet. -- Решите, нужны обычные данные RPC или архивные. -- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. +- сеть: `mainnet` или `testnet` +- роль узла: валидатор, RPC-узел или архивный узел +- ожидаете ли вы восстановление в единый каталог `~/.near/data` или в раздельную схему с горячим и холодным хранилищем +- целевой путь `NEAR_HOME` и схему хранилища, которую вы собираетесь использовать +- срочность, ограничения по пропускной способности или сроки восстановления, которые влияют на план передачи данных -- Установите `rclone` — скрипты загрузки от него зависят. +## Используйте этот раздел, когда -:::info[Установка `rclone`] -Установите `rclone` командой: +- нужно подтвердить текущую позицию FastNear по снапшотам для mainnet или testnet +- нужен правильный путь запроса архивного снапшота +- нужно понять, стоит ли использовать снапшоты или стандартный сценарий запуска узла NEAR -```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash -``` -::: - -## Что покрывает каждый путь +## Не используйте этот раздел, когда -- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. -- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. +- идёт запрос данных цепочки для приложения +- нужны свежие блоки, балансы, история или состояние контракта +- нужны общие рекомендации по продуктовому API, а не настройка оператором -Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +В этих случаях используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx) или [NEAR Data API](https://docs.fastnear.com/ru/neardata). ## Выберите сеть +На страницах ниже собран текущий статус и путь эскалации по каждой сети. Они больше не предполагают наличие публичного URL для самостоятельной загрузки. + - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) @@ -1697,132 +2178,44 @@ sudo -v ; curl https://rclone.org/install.sh | sudo bash # Mainnet -## Оптимизированный снапшот mainnet - -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и подходит для более узких задач. - -Узлы с достаточными ресурсами могут использовать значение `$RPC_TYPE=fast-rpc`. По умолчанию используется `rpc`. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `RPC_TYPE=fast-rpc` — включает оптимизированный режим -::: - -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## RPC-снапшот mainnet - -Это стандартный способ получить снапшот без оптимизированного режима из предыдущего раздела. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота mainnet. -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -::: - -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet bash -``` - -## Архивный снапшот mainnet - -:::warning -**Требует много времени и места на диске.** - -Подготовьтесь к очень большому объёму загрузки и длительному времени выполнения. - -Размер снапшота составляет около 60 ТБ, и он содержит более 1 миллиона файлов. +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -Перед запуском скрипта загрузки можно задать следующие переменные окружения: +## Для обычных RPC-узлов и валидаторов -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов mainnet. -По умолчанию скрипт ожидает следующие пути для данных: +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` -- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` +## Для архивных снапшотов mainnet -**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -1. Получите высоту блока последнего снапшота: +## Что важно знать о старых скриптах -`Latest archival mainnet snapshot block`: +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. +## Если вы готовитесь к запросу архивного снапшота -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +- Будьте готовы разнести hot data и cold data, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -`Archival Mainnet Snapshot (hot-data) » ~/.near/data`: +## Если FastNear передаст вам пакет снапшота -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -3. Скачайте данные COLD из снапшота. Их можно разместить на HDD. - -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=cold-data` — выбирает загрузку Cold data -- `DATA_PATH=/mnt/hdds/cold-data` — путь для размещения cold data. **Обратите внимание:** конфигурация nearcore должна указывать на тот же путь для cold data. -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: - -`Archival Mainnet Snapshot (cold-data) » /mnt/hdds/cold-data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=cold-data DATA_PATH=/mnt/hdds/cold-data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). --- @@ -1835,81 +2228,44 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/ # Testnet -## RPC-снапшот testnet - -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и нужен для более узких задач. +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота testnet. -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот testnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает данные testnet +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -`RPC Testnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=testnet bash -``` - -## Архивный снапшот testnet +## Для обычных RPC-узлов и валидаторов -:::warning -**Требует много времени и места на диске.** +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов testnet. -Подготовьтесь к большому объёму загрузки и длительному времени выполнения. -::: +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -Перед запуском скрипта загрузки можно задать следующие переменные окружения: +## Для архивных снапшотов testnet -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -По умолчанию скрипт ожидает следующий путь для данных: +## Что важно знать о старых скриптах -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -1. Получите высоту блока последнего снапшота: +## Если вы готовитесь к запросу архивного снапшота -`Latest archival testnet snapshot block`: +- Будьте готовы разместить hot data на быстром локальном хранилище, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +## Если FastNear передаст вам пакет снапшота -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает сеть testnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: - -`Archival Testnet Snapshot (hot-data) » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=testnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). --- diff --git a/static/ru/llms.txt b/static/ru/llms.txt index 985f6d7..dd5c02d 100644 --- a/static/ru/llms.txt +++ b/static/ru/llms.txt @@ -26,6 +26,7 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп - [Агенты на FastNear](https://docs.fastnear.com/ru/agents.md): Операционное руководство для ИИ-агентов и сред автоматизации, использующих документацию FastNear и её RPC- и REST-API. - [Аутентификация для агентов](https://docs.fastnear.com/ru/agents/auth.md): Операционная работа с учётными данными для ИИ-агентов, автоматизаций, воркеров и бэкенд-сред, использующих FastNear. - [Как выбрать подходящую поверхность](https://docs.fastnear.com/ru/agents/choosing-surfaces.md): Направьте задачу агента к сырому RPC, индексированным API, истории транзакций, NEAR Data, FastData или снапшотам. +- [Как построить MCP-сервер на FastNear](https://docs.fastnear.com/ru/agents/mcp.md): Используйте RPC и REST API FastNear внутри собственного MCP-сервера на TypeScript с примером на прямых HTTP-вызовах. - [Плейбуки для агентов](https://docs.fastnear.com/ru/agents/playbooks.md): Рабочие процессы FastNear по умолчанию для типовых задач ИИ-агентов и автоматизаций. ## Другие гайды @@ -35,9 +36,9 @@ AI-читабельные индексы для гайдов FastNear, RPC-сп ## Снапшоты -- [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Пути загрузки снапшотов FastNear для подъёма и восстановления узлов NEAR. -- [mainnet](https://docs.fastnear.com/ru/snapshots/mainnet.md): Скачайте RPC- и архивные снапшоты mainnet для быстрого развёртывания NEAR-инфраструктуры на базе FastNear. -- [testnet](https://docs.fastnear.com/ru/snapshots/testnet.md): Скачайте RPC- и архивные снапшоты testnet для быстрого развёртывания NEAR-инфраструктуры на базе FastNear. +- [Снапшоты для валидаторов](https://docs.fastnear.com/ru/snapshots.md): Текущий статус снапшотов FastNear и рекомендации для подъёма узлов NEAR и запросов архивных снапшотов. +- [mainnet](https://docs.fastnear.com/ru/snapshots/mainnet.md): Текущая позиция FastNear по снапшотам mainnet для RPC-подъёма и запросов архивных снапшотов. +- [testnet](https://docs.fastnear.com/ru/snapshots/testnet.md): Текущая позиция FastNear по снапшотам testnet для RPC-подъёма и запросов архивных снапшотов. ## RPC аккаунта diff --git a/static/ru/snapshots.md b/static/ru/snapshots.md index 39c4d48..c5552e9 100644 --- a/static/ru/snapshots.md +++ b/static/ru/snapshots.md @@ -5,49 +5,50 @@ Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. :::warning[Бесплатные снапшоты устарели] -Бесплатные снапшоты данных nearcore больше не выпускаются. +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). ::: -## Используйте этот раздел, когда - -- нужно поднять узел mainnet или testnet из данных снапшота -- идёт восстановление RPC- или архивного узла -- уже известно, что нужен путь загрузки снапшота FastNear - -## Не используйте этот раздел, когда +## Текущий статус -- идёт запрос данных цепочки для приложения -- нужны свежие блоки, балансы, история или состояние контракта -- нужны общие рекомендации по продуктовому API, а не настройка оператором +- Сейчас FastNear не публикует через этот сайт рабочий публичный путь для самостоятельной загрузки снапшотов. +- Для обычного запуска RPC-узлов и валидаторов используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему Epoch Sync вместе с децентрализованной синхронизацией состояния. +- Для доступа к архивным снапшотам переходите на [страницу FastNear о снапшотах](https://fastnear.com/snapshots) или пишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com). +- Исторические скрипты из [fastnear/static](https://github.com/fastnear/static) по-прежнему существуют, но прежний публичный поток с `latest.txt` больше не является поддерживаемым публичным сценарием. -В этих случаях используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx) или [NEAR Data API](https://docs.fastnear.com/ru/neardata). +## Полезные материалы для операторов -## Перед загрузкой +- [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync) — актуальный шаг для обновления списка boot nodes перед первым запуском. +- [NEAR Nodes: синхронизация состояния](https://near-nodes.io/rpc/state-sync) — текущая рекомендуемая схема запуска без снапшота. +- [NEAR Nodes: запуск архивного узла](https://near-nodes.io/archival/run-archival-node) — настройка архивного узла и ожидания по каталогу данных. +- [NEAR Nodes: раздельное хранилище для архивных узлов](https://near-nodes.io/archival/split-storage-archival) — если вы планируете современную схему с раздельным горячим и холодным хранилищем. -- Сначала выберите сеть: mainnet или testnet. -- Решите, нужны обычные данные RPC или архивные. -- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. +## Что стоит подготовить до запроса архивного снапшота -- Установите `rclone` — скрипты загрузки от него зависят. +- сеть: `mainnet` или `testnet` +- роль узла: валидатор, RPC-узел или архивный узел +- ожидаете ли вы восстановление в единый каталог `~/.near/data` или в раздельную схему с горячим и холодным хранилищем +- целевой путь `NEAR_HOME` и схему хранилища, которую вы собираетесь использовать +- срочность, ограничения по пропускной способности или сроки восстановления, которые влияют на план передачи данных -:::info[Установка `rclone`] -Установите `rclone` командой: +## Используйте этот раздел, когда -```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash -``` -::: +- нужно подтвердить текущую позицию FastNear по снапшотам для mainnet или testnet +- нужен правильный путь запроса архивного снапшота +- нужно понять, стоит ли использовать снапшоты или стандартный сценарий запуска узла NEAR -## Что покрывает каждый путь +## Не используйте этот раздел, когда -- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. -- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. +- идёт запрос данных цепочки для приложения +- нужны свежие блоки, балансы, история или состояние контракта +- нужны общие рекомендации по продуктовому API, а не настройка оператором -Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +В этих случаях используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx) или [NEAR Data API](https://docs.fastnear.com/ru/neardata). ## Выберите сеть +На страницах ниже собран текущий статус и путь эскалации по каждой сети. Они больше не предполагают наличие публичного URL для самостоятельной загрузки. + - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) diff --git a/static/ru/snapshots/index.md b/static/ru/snapshots/index.md index 39c4d48..c5552e9 100644 --- a/static/ru/snapshots/index.md +++ b/static/ru/snapshots/index.md @@ -5,49 +5,50 @@ Этот раздел — для операторов узлов, которые поднимают или восстанавливают инфраструктуру NEAR. Это не поверхность для прикладных данных. Если задача — читать балансы, историю, блоки или состояние контракта, используйте документацию API и RPC, а не сценарии со снапшотами. :::warning[Бесплатные снапшоты устарели] -Бесплатные снапшоты данных nearcore больше не выпускаются. +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. Infrastructure Committee и Near One рекомендуют Epoch Sync вместе с децентрализованной синхронизацией состояния. Актуальные рекомендации и режим подъёма смотрите на [NEAR Nodes](https://near-nodes.io). ::: -## Используйте этот раздел, когда - -- нужно поднять узел mainnet или testnet из данных снапшота -- идёт восстановление RPC- или архивного узла -- уже известно, что нужен путь загрузки снапшота FastNear - -## Не используйте этот раздел, когда +## Текущий статус -- идёт запрос данных цепочки для приложения -- нужны свежие блоки, балансы, история или состояние контракта -- нужны общие рекомендации по продуктовому API, а не настройка оператором +- Сейчас FastNear не публикует через этот сайт рабочий публичный путь для самостоятельной загрузки снапшотов. +- Для обычного запуска RPC-узлов и валидаторов используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему Epoch Sync вместе с децентрализованной синхронизацией состояния. +- Для доступа к архивным снапшотам переходите на [страницу FastNear о снапшотах](https://fastnear.com/snapshots) или пишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com). +- Исторические скрипты из [fastnear/static](https://github.com/fastnear/static) по-прежнему существуют, но прежний публичный поток с `latest.txt` больше не является поддерживаемым публичным сценарием. -В этих случаях используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx) или [NEAR Data API](https://docs.fastnear.com/ru/neardata). +## Полезные материалы для операторов -## Перед загрузкой +- [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync) — актуальный шаг для обновления списка boot nodes перед первым запуском. +- [NEAR Nodes: синхронизация состояния](https://near-nodes.io/rpc/state-sync) — текущая рекомендуемая схема запуска без снапшота. +- [NEAR Nodes: запуск архивного узла](https://near-nodes.io/archival/run-archival-node) — настройка архивного узла и ожидания по каталогу данных. +- [NEAR Nodes: раздельное хранилище для архивных узлов](https://near-nodes.io/archival/split-storage-archival) — если вы планируете современную схему с раздельным горячим и холодным хранилищем. -- Сначала выберите сеть: mainnet или testnet. -- Решите, нужны обычные данные RPC или архивные. -- Убедитесь, что понимаете, где должны лежать горячие и холодные данные, прежде чем стартовать архивную загрузку. +## Что стоит подготовить до запроса архивного снапшота -- Установите `rclone` — скрипты загрузки от него зависят. +- сеть: `mainnet` или `testnet` +- роль узла: валидатор, RPC-узел или архивный узел +- ожидаете ли вы восстановление в единый каталог `~/.near/data` или в раздельную схему с горячим и холодным хранилищем +- целевой путь `NEAR_HOME` и схему хранилища, которую вы собираетесь использовать +- срочность, ограничения по пропускной способности или сроки восстановления, которые влияют на план передачи данных -:::info[Установка `rclone`] -Установите `rclone` командой: +## Используйте этот раздел, когда -```bash -sudo -v ; curl https://rclone.org/install.sh | sudo bash -``` -::: +- нужно подтвердить текущую позицию FastNear по снапшотам для mainnet или testnet +- нужен правильный путь запроса архивного снапшота +- нужно понять, стоит ли использовать снапшоты или стандартный сценарий запуска узла NEAR -## Что покрывает каждый путь +## Не используйте этот раздел, когда -- **Mainnet** включает оптимизированный `fast-rpc`, обычный RPC и архивные пути загрузки для горячих и холодных данных. -- **Testnet** включает RPC и архивные пути снапшотов для операторов testnet. +- идёт запрос данных цепочки для приложения +- нужны свежие блоки, балансы, история или состояние контракта +- нужны общие рекомендации по продуктовому API, а не настройка оператором -Требования к узлам смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near), а исходники скриптов загрузки, которые используются в этих руководствах, — в [fastnear/static](https://github.com/fastnear/static). +В этих случаях используйте [Справочник RPC](https://docs.fastnear.com/ru/rpc), [FastNear API](https://docs.fastnear.com/ru/api), [Транзакции API](https://docs.fastnear.com/ru/tx) или [NEAR Data API](https://docs.fastnear.com/ru/neardata). ## Выберите сеть +На страницах ниже собран текущий статус и путь эскалации по каждой сети. Они больше не предполагают наличие публичного URL для самостоятельной загрузки. + - [Снапшоты mainnet](https://docs.fastnear.com/ru/snapshots/mainnet) - [Снапшоты testnet](https://docs.fastnear.com/ru/snapshots/testnet) diff --git a/static/ru/snapshots/mainnet.md b/static/ru/snapshots/mainnet.md index 4d95e90..f6fb939 100644 --- a/static/ru/snapshots/mainnet.md +++ b/static/ru/snapshots/mainnet.md @@ -2,129 +2,41 @@ # Mainnet -## Оптимизированный снапшот mainnet +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота mainnet. -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и подходит для более узких задач. - -Узлы с достаточными ресурсами могут использовать значение `$RPC_TYPE=fast-rpc`. По умолчанию используется `rpc`. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `RPC_TYPE=fast-rpc` — включает оптимизированный режим -::: - -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## RPC-снапшот mainnet - -Это стандартный способ получить снапшот без оптимизированного режима из предыдущего раздела. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet bash -``` - -## Архивный снапшот mainnet - -:::warning -**Требует много времени и места на диске.** - -Подготовьтесь к очень большому объёму загрузки и длительному времени выполнения. - -Размер снапшота составляет около 60 ТБ, и он содержит более 1 миллиона файлов. -::: +## Для обычных RPC-узлов и валидаторов -Перед запуском скрипта загрузки можно задать следующие переменные окружения: +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов mainnet. -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -По умолчанию скрипт ожидает следующие пути для данных: +## Для архивных снапшотов mainnet -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` -- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** +## Что важно знать о старых скриптах -1. Получите высоту блока последнего снапшота: +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -`Latest archival mainnet snapshot block`: +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +## Если вы готовитесь к запросу архивного снапшота -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. +- Будьте готовы разнести hot data и cold data, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: - -`Archival Mainnet Snapshot (hot-data) » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` - -3. Скачайте данные COLD из снапшота. Их можно разместить на HDD. - -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=cold-data` — выбирает загрузку Cold data -- `DATA_PATH=/mnt/hdds/cold-data` — путь для размещения cold data. **Обратите внимание:** конфигурация nearcore должна указывать на тот же путь для cold data. -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +## Если FastNear передаст вам пакет снапшота -`Archival Mainnet Snapshot (cold-data) » /mnt/hdds/cold-data`: +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=cold-data DATA_PATH=/mnt/hdds/cold-data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/static/ru/snapshots/mainnet/index.md b/static/ru/snapshots/mainnet/index.md index 4d95e90..f6fb939 100644 --- a/static/ru/snapshots/mainnet/index.md +++ b/static/ru/snapshots/mainnet/index.md @@ -2,129 +2,41 @@ # Mainnet -## Оптимизированный снапшот mainnet +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота mainnet. -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и подходит для более узких задач. - -Узлы с достаточными ресурсами могут использовать значение `$RPC_TYPE=fast-rpc`. По умолчанию используется `rpc`. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `RPC_TYPE=fast-rpc` — включает оптимизированный режим -::: - -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet RPC_TYPE=fast-rpc bash -``` - -## RPC-снапшот mainnet - -Это стандартный способ получить снапшот без оптимизированного режима из предыдущего раздела. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `RPC_TYPE` — `rpc` (по умолчанию) или `fast-rpc` -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот mainnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -`RPC Mainnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=mainnet bash -``` - -## Архивный снапшот mainnet - -:::warning -**Требует много времени и места на диске.** - -Подготовьтесь к очень большому объёму загрузки и длительному времени выполнения. - -Размер снапшота составляет около 60 ТБ, и он содержит более 1 миллиона файлов. -::: +## Для обычных RPC-узлов и валидаторов -Перед запуском скрипта загрузки можно задать следующие переменные окружения: +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов mainnet. -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -По умолчанию скрипт ожидает следующие пути для данных: +## Для архивных снапшотов mainnet -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` -- Cold data, которые можно хранить на HDD: `/mnt/nvme/data/cold-data` +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -**Выполните следующие команды, чтобы скачать архивный снапшот mainnet:** +## Что важно знать о старых скриптах -1. Получите высоту блока последнего снапшота: +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -`Latest archival mainnet snapshot block`: +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/mainnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +## Если вы готовитесь к запросу архивного снапшота -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. +- Будьте готовы разнести hot data и cold data, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: - -`Archival Mainnet Snapshot (hot-data) » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` - -3. Скачайте данные COLD из снапшота. Их можно разместить на HDD. - -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=cold-data` — выбирает загрузку Cold data -- `DATA_PATH=/mnt/hdds/cold-data` — путь для размещения cold data. **Обратите внимание:** конфигурация nearcore должна указывать на тот же путь для cold data. -- `CHAIN_ID=mainnet` — явно выбирает данные mainnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +## Если FastNear передаст вам пакет снапшота -`Archival Mainnet Snapshot (cold-data) » /mnt/hdds/cold-data`: +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=cold-data DATA_PATH=/mnt/hdds/cold-data CHAIN_ID=mainnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/static/ru/snapshots/testnet.md b/static/ru/snapshots/testnet.md index a0ce443..89cca48 100644 --- a/static/ru/snapshots/testnet.md +++ b/static/ru/snapshots/testnet.md @@ -2,78 +2,41 @@ # Testnet -## RPC-снапшот testnet +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота testnet. -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и нужен для более узких задач. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот testnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает данные testnet +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -`RPC Testnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=testnet bash -``` +## Для обычных RPC-узлов и валидаторов -## Архивный снапшот testnet +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов testnet. -:::warning -**Требует много времени и места на диске.** +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -Подготовьтесь к большому объёму загрузки и длительному времени выполнения. -::: - -Перед запуском скрипта загрузки можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +## Для архивных снапшотов testnet -По умолчанию скрипт ожидает следующий путь для данных: +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` +## Что важно знать о старых скриптах -**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -1. Получите высоту блока последнего снапшота: +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -`Latest archival testnet snapshot block`: +## Если вы готовитесь к запросу архивного снапшота -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +- Будьте готовы разместить hot data на быстром локальном хранилище, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. - -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает сеть testnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +## Если FastNear передаст вам пакет снапшота -`Archival Testnet Snapshot (hot-data) » ~/.near/data`: +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=testnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/static/ru/snapshots/testnet/index.md b/static/ru/snapshots/testnet/index.md index a0ce443..89cca48 100644 --- a/static/ru/snapshots/testnet/index.md +++ b/static/ru/snapshots/testnet/index.md @@ -2,78 +2,41 @@ # Testnet -## RPC-снапшот testnet +FastNear больше не публикует здесь публичный сценарий самостоятельной загрузки снапшота testnet. -Обычно это предпочтительный способ синхронизации. Архивный снапшот заметно больше и нужен для более узких задач. - -Перед запуском скрипта загрузки снапшота можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `~/.near/data`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. - -**Выполните эту команду, чтобы скачать RPC-снапшот testnet:** - -:::info -Будут заданы следующие переменные окружения: -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает данные testnet +:::warning[Бесплатные снапшоты устарели] +Бесплатные снапшоты данных nearcore были прекращены 1 июня 2025 года. ::: -`RPC Testnet Snapshot » ~/.near/data`: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone.sh | DATA_PATH=~/.near/data CHAIN_ID=testnet bash -``` +## Для обычных RPC-узлов и валидаторов -## Архивный снапшот testnet +Используйте [NEAR Nodes](https://near-nodes.io) и актуальную схему запуска на основе Epoch Sync вместе с децентрализованной синхронизацией состояния. Это публично рекомендуемый путь для обычного запуска и восстановления узлов testnet. -:::warning -**Требует много времени и места на диске.** +Перед первым `neard run` обновите `network.boot_nodes` по актуальной команде из [NEAR Nodes: Epoch Sync](https://near-nodes.io/intro/node-epoch-sync). Даже у свежескачанного `config.json` список `boot_nodes` может остаться пустым, и тогда узел зависнет на `Waiting for peers 0 peers`. -Подготовьтесь к большому объёму загрузки и длительному времени выполнения. -::: - -Перед запуском скрипта загрузки можно задать следующие переменные окружения: - -- `CHAIN_ID` — `mainnet` или `testnet` (по умолчанию: `mainnet`) -- `THREADS` — число потоков для загрузки. Используйте `128` для 10Gbps и `16` для 1Gbps (по умолчанию: `128`) -- `TPSLIMIT` — максимальное число новых HTTP-действий в секунду (по умолчанию: `4096`) -- `DATA_TYPE` — `hot-data` или `cold-data` (по умолчанию: `cold-data`) -- `BWLIMIT` — максимальная пропускная способность для загрузки, если её нужно ограничить (по умолчанию: `10G`) -- `DATA_PATH` — путь, куда будет загружен снапшот (по умолчанию: `/mnt/nvme/data/$DATA_TYPE`) -- `BLOCK` — высота блока нужного снапшота. Если не указать, будет загружен последний снапшот. +## Для архивных снапшотов testnet -По умолчанию скрипт ожидает следующий путь для данных: +Доступ к архивным снапшотам предоставляется по запросу. Начните со [страницы FastNear о снапшотах](https://fastnear.com/snapshots) или напишите на [почту команды FastNear по снапшотам](mailto:snapshots@fastnear.com), чтобы получить актуальную информацию о доступности, высоте блока и требованиях к хранилищу. -- Hot data, которые должны лежать на NVME: `/mnt/nvme/data/hot-data` +## Что важно знать о старых скриптах -**Выполните следующие команды, чтобы скачать архивный снапшот testnet:** +В репозитории [fastnear/static](https://github.com/fastnear/static) по-прежнему лежат исторические скрипты `down_rclone.sh` и `down_rclone_archival.sh`. Мы больше не публикуем для них copy-paste команды в документации, потому что прежние публичные URL обнаружения через `latest.txt` теперь ведут на информационную страницу и не являются поддерживаемым публичным сценарием. -1. Получите высоту блока последнего снапшота: +Если поддержка FastNear выдаст вам конкретную высоту блока и рабочий путь загрузки, используйте именно те хосты, блок и схему размещения данных, которые вам предоставят, а не старые публичные примеры. -`Latest archival testnet snapshot block`: +## Если вы готовитесь к запросу архивного снапшота -```bash -LATEST=$(curl -s "https://snapshot.neardata.xyz/testnet/archival/latest.txt") -echo "Latest snapshot block: $LATEST" -``` +- Будьте готовы разместить hot data на быстром локальном хранилище, если это потребуется для конкретного пакета снапшота. +- Заранее проверьте схему хранения nearcore до начала переноса данных. +- Устанавливайте `rclone` только если полученный от поддержки сценарий действительно на него опирается. -2. Скачайте данные HOT из снапшота. Их нужно разместить на NVME. - -:::info -Будут заданы следующие переменные окружения: -- `DATA_TYPE=hot-data` — выбирает загрузку Hot data -- `DATA_PATH=~/.near/data` — стандартный путь nearcore -- `CHAIN_ID=testnet` — явно выбирает сеть testnet -- `BLOCK=$LATEST` — указывает блок снапшота -::: +## Если FastNear передаст вам пакет снапшота -`Archival Testnet Snapshot (hot-data) » ~/.near/data`: +1. Остановите `neard`, прежде чем заменять данные снапшота или менять настройки архивного хранилища. +2. Уточните, рассчитан ли пакет на `~/.near/data` или на раздельные пути `hot-data` и `cold-data`. +3. До перезапуска проверьте, что `config.json` соответствует нужному режиму архивного узла. + Для архивных узлов NEAR Nodes считает критичными параметры `archive: true` и `tracked_shards: [0]`. +4. Если пакет использует раздельное хранилище, убедитесь, что пути в конфигурации совпадают с полученной схемой hot/cold, и только потом запускайте узел. +5. Перезапустите узел и проверьте, что он стартует без ошибок и продолжает синхронизацию, а не остаётся на устаревшем локальном состоянии. -```bash -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/fastnear/static/refs/heads/main/down_rclone_archival.sh | DATA_TYPE=hot-data DATA_PATH=~/.near/data CHAIN_ID=testnet BLOCK=$LATEST bash -``` +Требования к хранилищу nearcore и общие операторские предпосылки смотрите в [nearcore](https://github.com/near/nearcore?tab=readme-ov-file#about-near). diff --git a/static/ru/structured-data/site-graph.json b/static/ru/structured-data/site-graph.json index b6b609e..101db11 100644 --- a/static/ru/structured-data/site-graph.json +++ b/static/ru/structured-data/site-graph.json @@ -3854,6 +3854,19 @@ "routeType": "docs", "url": "https://docs.fastnear.com/ru/agents/choosing-surfaces" }, + { + "entityIds": { + "familyIds": [], + "mainEntityId": null, + "pageId": "https://docs.fastnear.com/ru/agents/mcp#page" + }, + "indexable": true, + "markdownMirrorUrl": "https://docs.fastnear.com/ru/agents/mcp.md", + "pageSchemaType": "TechArticle", + "route": "/ru/agents/mcp", + "routeType": "docs", + "url": "https://docs.fastnear.com/ru/agents/mcp" + }, { "entityIds": { "familyIds": [],